Closures, this & scope

Interactive exploration of lexical scope, closure-captured state, loop-variable bugs, and this binding rules.

1 · Closure counter factory

Each counter is created by makeCounter() and closes over its own private count. Increment one — the others stay unchanged. That is independent lexical environments, not shared globals.

function makeCounter(label) {
  let count = 0; // private — captured by returned closure
  return {
    inc() { count++; return count; },
    get() { return count; },
    label,
  };
}
global scope closure #1 { count: 0 } closure #2 { count: 0 }

Classic loop closure bug — var vs let

Three setTimeout callbacks scheduled in a loop. With var, all closures share one binding (logs 3,3,3). With let, each iteration gets a fresh binding (logs 0,1,2).

Click a button to run the loop demo.

2 · this binding explorer

The same function body resolves this differently depending on call site, not declaration site. Click each scenario to invoke the function and see what this becomes.

Binding priority (highest wins): 1. new → fresh object  ·  2. explicit call/apply/bind  ·  3. method call obj.fn()  ·  4. default (strict: undefined, sloppy: global)  ·  arrow functions: lexical this (no own binding)