Advanced30 min read

Scope & Closures

Understand how JavaScript scope works — global, function, and block scope — and master the powerful concept of closures.

Global & Function Scope

Scope determines where variables are accessible in your code. JavaScript has several types of scope, and understanding them is critical for writing bug-free code.

Global scope — variables declared outside any function or block are globally accessible:

javascript
const appName = 'Kodojo'; // global scope

function greet() {
  console.log(appName); // accessible here
}

console.log(appName); // accessible here too

Global variables are available everywhere, but having too many pollutes the global namespace and can cause naming conflicts. Avoid creating unnecessary globals.

Function scope — variables declared with var inside a function are only accessible within that function:

javascript
function calculate() {
  var result = 42;
  console.log(result); // 42
}

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

var is function-scoped — it does not care about blocks like if or for, only functions:

javascript
function example() {
  if (true) {
    var x = 10; // function-scoped, not block-scoped!
  }
  console.log(x); // 10 — x is accessible outside the if block
}

This is one of the reasons let and const were introduced — they behave more predictably.

Block Scope

let and const are block-scoped — they are only accessible within the nearest enclosing curly braces {}:

javascript
if (true) {
  let x = 10;
  const y = 20;
  console.log(x, y); // 10 20
}
console.log(x); // ReferenceError
console.log(y); // ReferenceError

This applies to any block — if, for, while, or even a standalone {}:

javascript
{
  let secret = 'hidden';
}
console.log(secret); // ReferenceError

Block scoping is especially important in for loops. With let, each iteration gets its own copy of the variable:

javascript
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Logs: 0, 1, 2 (correct!)

With var, all iterations share the same variable — a classic JavaScript gotcha:

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Logs: 3, 3, 3 (wrong!)

This is why modern JavaScript strongly prefers let and const over var.

The Scope Chain

When JavaScript encounters a variable, it looks it up through the scope chain — starting from the innermost scope and working outward:

javascript
const global = 'I am global';

function outer() {
  const outerVar = 'I am outer';

  function inner() {
    const innerVar = 'I am inner';
    console.log(innerVar);  // found in inner scope
    console.log(outerVar);  // found in outer scope
    console.log(global);    // found in global scope
  }

  inner();
}

outer();

The lookup order is: inner scopeouter scopeglobal scope. If the variable is not found anywhere, JavaScript throws a ReferenceError.

This means inner functions always have access to variables from their parent functions. This is the foundation of closures.

Variable shadowing occurs when an inner scope declares a variable with the same name as an outer one:

javascript
const x = 'outer';

function example() {
  const x = 'inner'; // shadows the outer x
  console.log(x);    // 'inner'
}

example();
console.log(x); // 'outer' — unchanged

The inner x hides (shadows) the outer x within that scope. The outer x is still intact outside the function.

Closures

A closure is a function that remembers and can access variables from its outer (enclosing) scope, even after the outer function has finished executing. Closures are created every time a function is created.

javascript
function createGreeter(greeting) {
  // greeting is captured by the closure
  return function(name) {
    return `${greeting}, ${name}!`;
  };
}

const sayHello = createGreeter('Hello');
const sayHi = createGreeter('Hi');

console.log(sayHello('Alice')); // 'Hello, Alice!'
console.log(sayHi('Bob'));      // 'Hi, Bob!'

Even though createGreeter has returned, sayHello and sayHi still remember their respective greeting values. This is the closure in action.

Common use cases for closures:

  1. Data privacy — hiding variables from outside access:

    javascript
    function makeCounter() {
      let count = 0; // private!
      return {
        increment() { count++; },
        getCount() { return count; },
      };
    }
  2. Factory functions — creating specialized functions:

    javascript
    function multiplier(factor) {
      return (n) => n * factor;
    }
    const double = multiplier(2);
    const triple = multiplier(3);
  3. Callbacks that remember context — event handlers, timers, etc.

  4. Partial application — pre-filling some arguments of a function.

Closures are one of the most powerful patterns in JavaScript. They enable modules, private state, and functional programming techniques.

Closures in Action

html
<p id="output"></p>

<script>
  // A counter factory using closures
  function makeCounter() {
    let count = 0; // private variable

    return {
      increment() {
        count++;
      },
      decrement() {
        count--;
      },
      getCount() {
        return count;
      },
    };
  }

  const counter = makeCounter();
  counter.increment();
  counter.increment();
  counter.increment();
  counter.decrement();

  // count is not accessible directly:
  // console.log(counter.count); // undefined

  // Only through the closure:
  document.getElementById('output').textContent =
    'Count: ' + counter.getCount(); // Count: 2
</script>

What is a closure?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.