Scope, Closures, and Hoisting
Understand lexical scope, closures that remember variables, hoisting rules, and the temporal dead zone — the core of JavaScript interviews.
Scope and closures are the bridge between syntax and interview questions. If you can explain why a callback still sees a variable after its outer function finished, you understand half of JavaScript's power and half of its memory behavior. This lesson ties together lexical scope, hoisting, TDZ, and practical closure patterns you will use in real features.
Lexical scope — where variables live
Scope is determined by where code is written (lexical scope), not where it runs. Inner blocks and functions can read outer variables; outer scopes cannot see inner declarations. Each function invocation creates a new scope object for its locals — that is why two calls to the same function do not share local let variables unless you intentionally close over shared outer state.
Learn AceDevHub — Ship fasterClosures — functions that remember their environment
A closure forms when a function is defined inside another function and the inner function references variables from the outer scope. The inner function keeps those references alive even after the outer call returns — the outer variables live on the heap as long as any inner function can still run. Closures enable private state, partial application, and React hooks remembering previous renders.
11
12
11
11Practical closure — once-style function
The counter factory is the canonical closure: createCounter's local count is not reachable from outside, but returned methods close over it. That is module-pattern privacy before ES modules — and the same idea behind let in a loop creating per-iteration bindings for async callbacks.
Connecting to database...Hoisting — declarations move, assignments do not
The once wrapper demonstrates why closures matter in production: called and result persist between invocations without global variables. Event listeners, debouncers, and singleton initializers use the same pattern — wrap mutable state in an outer function, expose only the controlled inner function.
Hi
undefined
ES2024Temporal Dead Zone (TDZ)
Hoisting is compile-time binding registration, not physical line movement. Function declarations hoist fully. var hoists name with undefined value until assignment executes. let/const hoist but enter the temporal dead zone — the name exists but access throws until the declaration line runs, preventing use-before-init bugs.
From the start of the block until the let or const line executes, the binding exists but cannot be accessed — that region is the TDZ. It prevents the var-era bug of using variables before initialization.
42
undefinedScope chain lookup
The example below demonstrates "Scope chain lookup" in runnable form. Read the comment lines first — they map each step to the mental model from earlier sections, then trace execution top to bottom as the engine would.
inner
outer
globalChapter 2 checkpoint
Scope chain lookup walks outward from inner to outer until it finds a matching name or hits global. Shadowing (inner const level hiding outer level) is legal but should be used sparingly — reuse the same name only when it genuinely refers to the same concept at different layers.
You can compare values safely, branch with conditionals, iterate collections, write functions three ways, and explain closures and hoisting — the vocabulary of every JavaScript interview. Chat 3 moves into arrays, objects, destructuring, prototypes, and modules.
- 1=== for daily checks; Object.is for NaN and -0
- 2Closures = function + remembered outer variables
- 3let/const have TDZ; var hoists as undefined
- 4Each for (let i ...) iteration creates its own closure binding
- 5Next: arrays, objects, and destructuring patterns