Private Variables with Closures
Hide state behind a function scope.
Private Variables with Closures is a free JavaScript Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the JavaScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Privacy Through Closures
JavaScript long lacked a built-in way to make object fields private. Closures provide one: variables inside a function are invisible outside, yet accessible to functions defined within.
function secretBox() {
let secret = "hidden";
return {
reveal: () => secret
};
}
const box = secretBox();
console.log(box.reveal()); // "hidden"
console.log(box.secret); // undefinedA Counter With Private State
The canonical example: a counter whose count cannot be tampered with directly. Only the returned methods can change it.
function makeCounter() {
let count = 0;
return {
increment: () => ++count,
value: () => count
};
}
const c = makeCounter();
c.increment();
c.increment();
console.log(c.value()); // 2The State Is Untouchable
There is no way to reach count from outside. Attempts to read it return undefined; you must go through the methods.
function makeCounter() {
let count = 0;
return { inc: () => ++count, get: () => count };
}
const c = makeCounter();
c.count = 100; // creates an unrelated property
console.log(c.get()); // 0 - real count untouchedEnforcing Rules
Because access goes through methods, you can enforce invariants, such as never letting a balance go negative.
function account(start) {
let balance = start;
return {
withdraw: (n) => {
if (n > balance) return "insufficient";
balance -= n;
return balance;
},
balance: () => balance
};
}
const a = account(100);
console.log(a.withdraw(150)); // "insufficient"
console.log(a.withdraw(40)); // 60Multiple Private Fields
A closure can guard several variables at once. They are all hidden, shared only among the returned methods.
function user(name) {
let logins = 0;
let lastSeen = null;
return {
login: () => { logins++; lastSeen = "now"; },
stats: () => ({ name, logins, lastSeen })
};
}
const u = user("Ada");
u.login();
console.log(u.stats()); // { name: "Ada", logins: 1, lastSeen: "now" }Private Helper Functions
Not just data; you can hide internal functions too. Only the methods you expose are public.
function temperature() {
let celsius = 0;
const toF = (c) => c * 9 / 5 + 32; // private
return {
set: (c) => { celsius = c; },
fahrenheit: () => toF(celsius)
};
}
const t = temperature();
t.set(100);
console.log(t.fahrenheit()); // 212Independent Instances
Each call to the factory creates separate private state, so instances never interfere with one another.
function makeCounter() {
let n = 0;
return { inc: () => ++n };
}
const a = makeCounter();
const b = makeCounter();
console.log(a.inc(), a.inc()); // 1 2
console.log(b.inc()); // 1Read-Only Exposure
Expose a getter without a setter to make a value read-only from the outside while still mutable internally.
function timer() {
let ticks = 0;
setupInternally();
function setupInternally() { ticks = 3; }
return { ticks: () => ticks };
}
const t = timer();
console.log(t.ticks()); // 3 - no way to set it outsideComparison: Class Fields
Modern JavaScript also supports #private class fields. Closures remain useful for factory functions and when you want privacy without classes.
class Counter {
#count = 0;
inc() { return ++this.#count; }
}
const c = new Counter();
console.log(c.inc()); // 1Why It Works
The returned methods are closures over the outer variables. The outer function has returned, but its scope lives on because those closures still reference it.
A Memoization Cache
Private state is great for caches. The cache object stays hidden, persisting between calls.
function memoSquare() {
const cache = {};
return (n) => {
if (cache[n] === undefined) cache[n] = n * n;
return cache[n];
};
}
const sq = memoSquare();
console.log(sq(4)); // 16
console.log(sq(4)); // 16 (cached)Quick Check
Private variables with closures.
Recap
Closures create privacy: variables inside a factory are unreachable from outside but shared among the returned methods. This enables tamper-proof counters, balances with invariants, hidden helpers, and caches. Each factory call yields independent state. Modern #private class fields are an alternative, but closures shine for factory-style code.
Frequently asked questions
Is the “Private Variables with Closures” lesson free?
Yes — the full text of “Private Variables with Closures” is free to read here on the web, and the JavaScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the JavaScript Academy course, upgrade to CoddyKit PRO.
What will I learn in “Private Variables with Closures”?
Hide state behind a function scope. You practise JavaScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start JavaScript Academy?
No prior experience is required. JavaScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Private Variables with Closures” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this JavaScript Academy lesson?
Yes. Every JavaScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- How Closures Capture State
- Private Variables with Closures
- The Module Pattern (IIFE)
- Factory Functions