JavaScript Academy · Lesson

Private Variables with Closures

Hide state behind a function scope.

Lesson 2 of 413 steps

Private Variables with Closures is a free JavaScript Academy lesson on CoddyKit. This is lesson 2 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the JavaScript Academy learning path, and your progress syncs across the web and the CoddyKit app. The JavaScript Academy course includes 4 lessons in total.

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);   // undefined

A 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()); // 2

The 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 untouched

Enforcing 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));  // 60

Multiple 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()); // 212

Independent 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());          // 1

Read-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 outside

Comparison: 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()); // 1

Why 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.

Free to start

Learn JavaScript with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
59
Lessons
200

Frequently Asked Questions

Is the “Private Variables with Closures” lesson free?

Yes — any 3 lessons of the JavaScript Academy learning path, including “Private Variables with Closures”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The JavaScript Academy course includes 4 lessons in total.

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, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.

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

  1. How Closures Capture State
  2. Private Variables with Closures
  3. The Module Pattern (IIFE)
  4. Factory Functions
← Back to JavaScript Academy