Back to JavaScript
2025-12-157 min read

Function scopes and closures (JavaScript)

Learn Function scopes and closures (JavaScript) step by step with clear examples and exercises.

Why This Matters

Welcome to this full guide on understanding Function Scopes and Closures in JavaScript! This lesson aims to provide a deep understanding of these fundamental concepts, which are crucial for writing efficient, maintainable, and robust JavaScript code. By the end of this tutorial, you'll be well-equipped to master function scopes, closures, avoid common pitfalls, and use their power in your projects.

Why This Matters

The significance of understanding Function Scopes and Closures can be summarized as follows:

  1. Code organization: Functions help organize your code by encapsulating related statements and variables, making it easier to manage and maintain.
  2. Data privacy: By defining different scopes, you can control the visibility of your variables, ensuring that they are not accidentally modified or accessed by other parts of your code.
  3. Reusable code: Functions allow you to create reusable blocks of code that can be called multiple times with different arguments, reducing redundancy and improving efficiency.
  4. Closures enable higher-order functions: Closures are essential for creating higher-order functions, such as map(), filter(), and reduce(), which are fundamental building blocks in functional programming.
  5. Avoiding common bugs: Understanding Function Scopes and Closures can help you avoid common pitfalls that can lead to hard-to-debug issues, making your development process smoother and more efficient.
  6. Leveraging advanced patterns: Mastery of closures enables you to implement advanced programming patterns like the Module Pattern and Immediately Invoked Function Expressions (IIFEs), which promote code modularity and encapsulation.

Prerequisites

To get the most out of this lesson, you should have a solid understanding of:

  1. JavaScript syntax, including variables, data types, operators, and control structures.
  2. The concept of functions in JavaScript, including function declarations and expressions.
  3. Variable hoisting in JavaScript.
  4. Understanding the difference between let, const, and var in JavaScript.
  5. Basic knowledge of ES6 features like arrow functions, template literals, and destructuring assignments.

Core Concept

Function Scopes

In JavaScript, every variable has a scope, which defines where it can be accessed within the code. There are two types of scopes: global and local.

Global Scope

Any variable declared outside of any function or block is considered to be in the global scope. These variables can be accessed from anywhere within your script.

let globalVariable = "I'm in the global scope";

function testFunction() {
console.log(globalVariable); // Output: I'm in the global scope
}

testFunction();

Local Scope

Variables declared within a function or block are considered to be in the local scope. These variables can only be accessed from within the function or block they were defined in.

function testFunction() {
let localVariable = "I'm in the local scope";
}

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

Block Scopes

With ES6, JavaScript introduced block scoping through the use of let and const. Variables declared with these keywords are only accessible within the block they are defined in, even if no curly braces ({}) are present.

if (true) {
let blockVariable = "I'm in a block";
}

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

Function Scopes and Hoisting

Unlike variable declarations, variable assignments are not hoisted in JavaScript. This means that if you try to access a variable before it's declared, you will get undefined. However, function declarations _are_ hoisted, which can lead to unexpected behavior when working with closures.

console.log(myFunction()); // Output: undefined (because the function is hoisted)
function myFunction() {
return "I'm a function";
}

Closures

A closure is a function that has access to its parent function's variables, even after the parent function has completed execution. This allows you to create functions that retain access to their enclosing scope's variables, which can be very useful for various programming patterns and techniques.

function outerFunction(outerVariable) {
return function innerFunction() {
console.log(`Outer variable: ${outerVariable}`);
};
}

let newFunction = outerFunction("I'm an outer variable");
newFunction(); // Output: Outer variable: I'm an outer variable

Worked Example

Let's create a more complex example to demonstrate how closures can be used to create higher-order functions and implement advanced patterns. We will create a simple implementation of the Module Pattern, which promotes code modularity and encapsulation.

const myModule = (function () {
let privateVariable = "I'm a private variable";

function innerFunction() {
console.log(privateVariable);
}

return {
publicMethod: function () {
innerFunction();
},
getPrivateVariable: function () {
return privateVariable;
},
};
})();

console.log(myModule.publicMethod()); // Output: I'm a private variable
console.log(myModule.getPrivateVariable()); // ReferenceError: privateVariable is not defined (because it's private)

In this example, the Module Pattern creates an immediately invoked function expression (IIFE), which defines a private variable and inner function. The IIFE returns an object with public methods that can be accessed from outside, while the private variables and inner functions remain hidden.

Common Mistakes

  1. Forgetting to return a value from a closure: If you forget to return a value from the inner function in a closure, the returned object will not have any observable properties or methods.
function outerFunction() {
let privateVariable = "I'm a private variable";

function innerFunction() {
console.log(privateVariable);
}

// Forgot to return the value!
}

let newFunction = outerFunction();
newFunction(); // TypeError: Cannot read properties of undefined (reading 'length')
  1. Misunderstanding variable hoisting: Variable declarations are hoisted in JavaScript, but assignments are not. This can lead to unexpected behavior when working with closures.
function outerFunction() {
let privateVariable; // Declaration is hoisted

function innerFunction() {
console.log(privateVariable); // undefined (because assignment hasn't happened yet)
privateVariable = "I'm a private variable";
}

return innerFunction;
}

let newFunction = outerFunction();
newFunction(); // Output: undefined
  1. Using var instead of let or const: Using the var keyword can lead to issues with function scopes, as it follows the Function Hoisting rule and can create global variables when used within a function. It is recommended to use let or const for better control over variable scope.
function testFunction() {
var localVariable = "I'm in the local scope";
}

testFunction();
console.log(localVariable); // Output: I'm in the local scope (because of function hoisting)
  1. Not understanding let, const, and var differences: Using let and const instead of var can help prevent unintended variable reassignments, as they each have different scoping rules and behavior in JavaScript.

Practice Questions

  1. Write a function that takes an array of numbers and returns a new function that can be used to find the sum of the first n elements in the array.
function sumArray(arr) {
let total = 0;

return function findSum(n) {
if (n > arr.length) throw new Error("Index out of bounds");
for (let i = 0; i < n; i++) {
total += arr[i];
}
return total;
};
}

const myArray = [1, 2, 3, 4, 5];
const sumFunction = sumArray(myArray);
console.log(sumFunction(3)); // Output: 6 (1 + 2 + 3)
  1. Create a function that generates a sequence of Fibonacci numbers up to a given number n. The function should return an array containing the generated Fibonacci numbers.
function fibonacciSequence(n) {
let fib = [0, 1];

for (let i = 2; i <= n; i++) {
fib.push(fib[i - 1] + fib[i - 2]);
}

return fib;
}

console.log(fibonacciSequence(10)); // Output: [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]

FAQ

  1. Why do closures matter in JavaScript?

Closures are essential for creating higher-order functions and encapsulating data within functions, making it possible to create modular, reusable code. They also enable various programming patterns, such as the Module Pattern and Immediately Invoked Function Expressions (IIFEs).

  1. What is the difference between a closure and a callback?

A closure is a function that has access to its parent function's variables, while a callback is a function passed as an argument to another function to be executed at a later time. Although closures can be used as callbacks, they serve different purposes and are not the same concept.

  1. How do I create a closure in JavaScript?

A closure is created whenever a nested function references variables from its parent scope. This can happen when you define a function within another function or when you return a function that references variables from its enclosing scope.

  1. What happens to the variables of a closure when the outer function returns?

The variables of a closure remain accessible even after the outer function has returned, as long as the inner (closure) function remains active. This is one of the key features that make closures so powerful in JavaScript.

  1. What is the difference between let, const, and var in JavaScript?

In JavaScript, let and const are block-scoped variables that can be reassigned (let) or cannot be reassigned (const), while var follows Function Hoisting rules and can create global variables when used within a function. Using let or const is recommended for better control over variable scope.

Function scopes and closures (JavaScript) | JavaScript | XQA Learn