JavaScript: Scope and Closures

Understand where variables live, how JavaScript looks them up, and how closures let functions remember their environment.

Global Scope

A variable declared outside of any function or block lives in the global scope and is accessible from anywhere in the script.

Global Scope

Global variables are convenient but risky: any code can read or overwrite them.

  • Variables declared with var at the top level become properties of window in browsers
  • Top-level let and const are global in reach but not attached to window
  • Avoid polluting global scope: name collisions across scripts cause hard-to-trace bugs

Global Scope Example

A top-level variable is readable inside any function.

Function Scope

Variables declared with var (or let / const) inside a function are local to that function and cannot be accessed outside it.

Function Scope

Each function creates its own private scope, protecting its variables from the outside world.

  • A variable declared inside a function disappears when the function returns
  • var is scoped to the nearest enclosing function, not to blocks
  • Function scope is the reason helper variables do not leak into global scope

Function Scope Example

rate is private to calculateTax and invisible outside it.

Block Scope (let and const)

let and const are scoped to the nearest pair of curly braces {}, including if blocks, loops, and standalone blocks.

Block Scope

Block scope prevents loop and if-statement variables from leaking into the surrounding function.

  • var ignores block boundaries, it leaks out of if/for blocks into the function
  • let and const are confined to the block { } they are declared in
  • Prefer let / const over var to avoid scope surprises

Block Scope vs var

let stays inside the block; var leaks into the enclosing function.

Lexical Scope

JavaScript uses lexical (static) scope: a function's scope is determined by where it is written in the source code, not where or how it is called.

Lexical Scope

The scope of a function is fixed at write time, not at call time.

  • An inner function can always read variables from its outer function
  • The lookup happens at parse time based on the code structure
  • This is the foundation that makes closures possible

Lexical Scope Example

inner() sees language and level because of where it is written, not where it is called.

Scope Chain

When JavaScript looks up a variable, it starts in the current scope and works outward through each enclosing scope until it finds the variable or reaches global scope.

Scope Chain

The scope chain is the ordered list of scopes JavaScript searches when resolving a variable name.

  • Search order: current scope → outer function scope → ... → global scope
  • If the variable is not found anywhere, a ReferenceError is thrown
  • The chain only goes outward: inner scopes are not visible to outer ones

Scope Chain Lookup

inner() walks the chain outward to find a and b.

What is a Closure?

A closure is a function that retains access to its outer scope variables even after the outer function has finished executing. The function "closes over" its surrounding environment.

Closure

A closure bundles a function together with the variables from the scope where it was defined.

  • The outer function runs and returns, but its variables are not garbage-collected if an inner function still references them
  • Every function in JavaScript is technically a closure
  • Closures are what allow callbacks and event handlers to remember data from their creation context

Basic Closure

Each returned function remembers its own name from when makeGreeter was called.

How Closures Work

Each call to the outer function creates a new scope with its own copy of the variables. The returned inner function holds a reference to that specific scope.

Closure Mechanics

The inner function holds a live reference to the outer scope, not a snapshot copy.

  • Each outer function call produces an independent closure environment
  • The inner function reads the current value of the outer variable, not the value at creation time
  • This is why closures over loop variables with var behave unexpectedly, all share one variable

Independent Closure Environments

c1 and c2 each close over their own count variable.

Practical Uses of Closures

Closures appear constantly in JavaScript: callbacks, event handlers, timers, and factory functions all rely on them.

Common Closure Use Cases

Closures let you attach persistent state to a function without a class.

  • Event handlers that need access to variables from the setup code
  • setTimeout / setInterval callbacks that reference outer data
  • Factory functions that produce pre-configured functions
  • Memoization: caching function results in a closed-over object

Closures in Practice

A multiplier factory and a delayed logger both use closures to capture their context.

Private Variables with Closures

Because inner functions can read outer variables but outside code cannot, closures are the classic JavaScript way to create truly private state.

Private State via Closures

Variables in the outer function scope are inaccessible to any code outside the returned object.

  • Only the functions returned by the outer function can read or mutate the private variable
  • Outside code has no way to reach the variable directly
  • This pattern predates the #privateField class syntax and still works everywhere

Private Balance

balance is only readable through the returned methods, not directly.

Module Pattern

The module pattern wraps related code in an immediately-invoked function expression (IIFE) and returns only the public API, keeping internals private.

Module Pattern

The module pattern uses a closure to simulate a module with public and private members.

  • An IIFE runs immediately and its scope is the private closure environment
  • Only properties on the returned object are public
  • Widely used before ES modules (import/export) were available
  • Still useful for quick encapsulation in scripts that do not use a bundler

Module Pattern Example

items and total() are private; only add, getTotal, and itemCount are exposed.

Variable Shadowing

When an inner scope declares a variable with the same name as one in an outer scope, the inner declaration shadows the outer one within that scope.

Variable Shadowing

Shadowing is not an error, but accidental shadowing causes subtle bugs where you expect to see the outer value.

  • The outer variable is not changed, it is simply hidden inside the inner scope
  • Once you leave the inner scope, the outer variable is visible again at its original value
  • Intentional shadowing is fine; accidental shadowing (parameter names matching globals) is a common mistake

Variable Shadowing

The inner color hides the outer one, but only within paintWall's scope.

Knowledge Check

1. Where is a variable declared with var at the top level of a script accessible?

2. Which keywords create block scope?

3. What is lexical scope?

4. What is a closure?

5. What does the scope chain do when a variable is not found in the current scope?

6. Which pattern uses closures to hide internal variables from outside code?

7. What is variable shadowing?