Codeskill

Learn to code, step by step

Understanding JavaScript Scope and Closure

A look at scope and closure in JavaScript: where variables are visible, and why an inner function can still read variables from the function that created it.

What is scope?

Scope is about where a variable can be accessed. There are two main types: global and local.

Global scope

A variable declared outside any function has global scope. It can be accessed anywhere in your code.

Example

let globalVar = "I am global";

function testScope() {
    console.log(globalVar);  // Accessible here
}

console.log(globalVar);  // And here

Local scope

Variables declared inside a function are locally scoped. They only exist within that function.

Example

function testLocalScope() {
    let localVar = "I am local";
    console.log(localVar);  // Accessible here
}

testLocalScope();
// console.log(localVar);  // Error: localVar is not defined outside the function

Block scope in ES6

ES6 introduced let and const, which are scoped to the block they are declared in – an if statement, a loop, and so on.

Example

if (true) {
    let blockVar = "I am block-scoped";
    console.log(blockVar);  // Accessible here
}

// console.log(blockVar);  // Error: blockVar is not defined outside the block

Understanding closure

A closure happens when a function remembers and can access variables from its outer scope, even after that outer function has finished running.

Example

function makeGreeting() {
    let name = "Alice";

    return function() {
        console.log("Hello " + name);
    };
}

let greetAlice = makeGreeting();  // The function makeGreeting has returned
greetAlice();  // Outputs: Hello Alice

greetAlice is a closure. It still has access to name from makeGreeting, even though makeGreeting has already returned.

Why are closures useful?

Closures are useful for several reasons:

  1. Data encapsulation: create private variables that only inner functions can access.
  2. Maintaining state: keep values available in callbacks and asynchronous code.
  3. Currying and function factories: build functions that return other functions with preset values.

Example – function factory

function makeMultiplier(multiplier) {
    return function (number) {
        return number * multiplier;
    };
}

let double = makeMultiplier(2);
console.log(double(5));  // Outputs: 10

Common pitfalls with closure

Closures inside loops are a common source of bugs.

Example of a pitfall

for (var i = 1; i <= 3; i++) {
    setTimeout(function() {
        console.log("i: " + i);  // Outputs "i: 4" three times, not the expected 1, 2, 3
    }, i * 1000);
}

The variable i is shared across every iteration. By the time the setTimeout callbacks run, the loop has finished and i is 4.

Solution using IIFE (Immediately Invoked Function Expression)

for (var i = 1; i <= 3; i++) {
    (function(j) {
        setTimeout(function() {
            console.log("j: " + j);  // Correctly outputs 1, 2, 3
        }, j * 1000);
    })(i);
}

Scope and closure sit at the heart of how JavaScript manages variables. Once they click, a lot of patterns in real code start to make sense.

PreviousDiving into Objects: The Building Blocks of JavaScript