Phase 2 · Core JavaScriptModule 10~42 min read

Closures & Private State

Use closures to preserve state, build factories, and understand one of JavaScript's defining features.

What you'll learn

A closure is a function paired with the lexical environment where it was created. It lets behavior carry durable, private state without exposing that state globally.

By the end of this lesson, you'll be able to:

  • Explain what a closure retains
  • Build stateful factory functions
  • Recognize closure lifetime and memory tradeoffs

Core mental model

Use this decision table as a compact reference. Focus on what each tool means and when it earns its place in production code.

ConceptWhat it meansDecision rule
Captured bindingA function retains access to outer namesCapture only state the returned behavior needs
FactoryEach call creates independent state and behaviorUse factories for configurable instances
Private stateData is reachable only through returned functionsExpose operations instead of raw mutable data

Professional workflow

Build the behavior in small, observable steps. Each step should leave something you can inspect or test.

  1. Describe the closure boundary: inputs, outputs, state, timing, and expected failures.
  2. Implement the smallest correct path with names that expose intent.
  3. Add edge cases and failure handling before introducing abstractions.
  4. Verify behavior with realistic data and one deliberately adversarial example.
  5. Refactor only after the observable behavior is protected.

Make behavior observable

Before optimizing or abstracting, make inputs, outputs, state changes, timing, and failure paths visible. JavaScript becomes much easier to reason about when hidden work is exposed.

Guided code lab

Create an independent counter

Every factory call receives a separate count binding.

counter.js
function createCounter(start = 0) {
  let count = start;
  return {
    next() { count += 1; return count; },
    current() { return count; },
  };
}
const counter = createCounter(10);
console.log(counter.next(), counter.next(), counter.current());

Configure a reusable validator

The rule is captured once and applied to many values.

validator.js
function atLeast(minimum) {
  return value => value >= minimum;
}
const isAdult = atLeast(18);
console.log(isAdult(16));
console.log(isAdult(21));

Production practice

Contract

Returned functions form the public API; captured state remains an implementation detail.

Verification

Create two instances and prove their state evolves independently.

Operations

Release references to long-lived closures when their work is finished.

Common failure mode

Capturing a large object or DOM tree can keep it alive long after the visible feature is removed.

Independent workshop

Build a shopping-cart factory with add, remove, total, and snapshot operations.

Your finished workshop must include:

  • Private item storage
  • Independent cart instances
  • Snapshots that callers cannot mutate

Definition of done

Demonstrate the happy path and at least two edge cases, keep responsibilities separated, and add a short note explaining one design choice.

Recap & quick check

Key takeaways

  • Closures retain lexical access
  • Factories create independent environments
  • Private state is exposed through operations
  • Retained references affect memory lifetime

Quick check

1. What does a closure preserve?

2. Why call a factory twice?

3. When can captured state be collected?

Keep the workshop. Later modules deliberately build on these decisions, so each exercise can become part of your final portfolio architecture.