Contents
  1. What Scope Is
  2. Global Scope
  3. Function Scope
  4. Block Scope
  5. The Scope Chain
  6. Hoisting
  7. What Closure Is
  8. What to Do Now
← All posts

What Is Scope and What Is Closure in JavaScript?

Scope defines where a variable is accessible. Closure is what forms when a function carries its scope beyond its original context. Understanding scope is the prerequisite.

Scope and closure are two of the most examined concepts in JavaScript. They are directly related. Understanding scope is a prerequisite for understanding closures, because a closure is what forms when a function carries its scope with it beyond its original context.

What Scope Is

Scope defines where a variable is accessible in a program. A variable declared in one part of a program is not necessarily reachable from another. The location of the declaration determines the boundary of access.

JavaScript has three types of scope.

Global Scope

A variable declared outside any function or block exists in the global scope. It is accessible from anywhere in the program. In a browser environment, globally scoped variables declared with var become properties of the window object.

var language = 'JavaScript';

function print() {
  console.log(language); // accessible
}

print(); // 'JavaScript'

Function Scope

A variable declared inside a function is scoped to that function. It does not exist outside of it.

function greet() {
  var message = 'Hello';
  console.log(message); // 'Hello'
}

greet();
console.log(message); // ReferenceError: message is not defined

Block Scope

Introduced in ES6, block scope applies to variables declared with let and const. A block is any pair of curly braces, including if statements, for loops, and standalone blocks.

if (true) {
  let blockVar = 'inside';
  console.log(blockVar); // 'inside'
}

console.log(blockVar); // ReferenceError: blockVar is not defined

var does not respect block scope. It is function-scoped, which means it leaks out of blocks:

if (true) {
  var leaked = 'I escaped';
}

console.log(leaked); // 'I escaped'

This behaviour of var is a common source of bugs in JavaScript and is one of the reasons let and const were introduced.

The Scope Chain

When JavaScript cannot find a variable in the current scope, it looks up to the next outer scope, and continues upward until it reaches the global scope or throws a ReferenceError. This traversal is the scope chain. The lookup is unidirectional: inner scopes can read outer variables, but outer scopes cannot read inner variables.

const outer = 'outer value';

function middle() {
  const mid = 'middle value';

  function inner() {
    console.log(outer); // found via scope chain
    console.log(mid);   // found via scope chain
  }

  inner();
}

middle();

Hoisting

JavaScript moves variable and function declarations to the top of their scope before execution. This is called hoisting.

var declarations are hoisted and initialised as undefined. Accessing a var variable before its declaration line returns undefined rather than throwing an error.

let and const are hoisted but not initialised. Accessing them before their declaration line throws a ReferenceError. The period between entering the scope and the declaration line is called the Temporal Dead Zone.

console.log(a); // undefined (var is hoisted)
var a = 5;

console.log(b); // ReferenceError (let is in TDZ)
let b = 5;

Function declarations are fully hoisted, meaning the entire function is available before its declaration line. Function expressions assigned to variables follow the rules of the variable keyword used.

What Closure Is

A closure forms when a function retains access to the variables of its outer scope even after that outer scope has finished executing. According to the MDN Web Docs, a closure is the combination of a function bundled together with references to its surrounding state, the lexical environment.

function outer() {
  const count = 0;

  return function inner() {
    console.log(count);
  };
}

const fn = outer();
fn(); // 0

outer has finished executing. Under normal circumstances, count would be discarded. Because inner was returned and holds a reference to count in its closure, the variable persists in memory.

Closures are most commonly used to create private state:

function createCounter() {
  let count = 0;

  return {
    increment() { count++; },
    value()     { return count; }
  };
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.value()); // 2
console.log(count);           // ReferenceError

count is maintained across calls but is not accessible from outside. That is the core utility of a closure: state that persists, scoped privately to the function that created it.

What to Do Now

Test the Temporal Dead Zone directly:

{
  console.log(x); // ReferenceError
  let x = 10;
}

Then replace let with var and observe the difference. Understanding that distinction is the practical difference between predictable and unpredictable variable behaviour in JavaScript.

← All posts