Javascript Scope Hoisting And Closures Complete Guide

 Last Update:2025-06-22T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    8 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of JavaScript Scope, Hoisting, and Closures

JavaScript Scope, Hoisting, and Closures: A Comprehensive Guide

Types of Scope in JavaScript

  1. Global Scope:

    • Variables declared outside any function or block are in the global scope.
    • These can be accessed from anywhere in the code.
    • Example:
      var globalVar = "I am Global";
      
      function printGlobalVar() {
          console.log(globalVar); // Accessible here
      }
      
  2. Function Scope:

    • Variables declared inside a function using var are in the function’s local scope.
    • They are accessible only within that specific function.
    • Example:
      function printLocalVar() {
          var localVar = "I am Local";
          console.log(localVar); // Accessible here
      }
      
      console.log(localVar); // Unaccessible here, will throw an error
      
  3. Block Scope (ES6+):

    • Introduced with let and const, block-scoped variables are only accessible within the {} block they are declared in.
    • Example:
      if (true) {
          let blockVar = "I am Block Scoped";
          const blockConst = "I am also Block Scoped";
      }
      
      console.log(blockVar); // Unaccessible here, will throw an error
      console.log(blockConst); // Unaccessible here, will throw an error
      
  4. Lexical Scope:

    • Each function or block creates a new lexical environment and has its own scope.
    • Functions can access variables in their parent scope, following a hierarchy up to the global scope but not vice versa.
    • Example:
      function outerFunction() {
          var outerVar = "I am Outer";
      
          function innerFunction() {
              console.log(outerVar); // Accessible here
          }
      
          innerFunction();
      }
      
      outerFunction();
      console.log(outerVar); // Unaccessible here, will throw an error
      

Hoisting

Hoisting refers to the behavior of variable and function declarations being moved to the top of their respective scopes automatically by the JavaScript engine during the compilation phase. This can lead to some counterintuitive behaviors if not understood properly.

  • Variable Hoisting:

    • Only declarations are hoisted, not initializations.
    • Using var results in undefined. With let and const, it results in the Temporal Dead Zone (TDZ) where the variable cannot be accessed until the declaration is encountered.
    • Example:
      console.log(x); // undefined
      var x = 5;
      
      console.log(y); // ReferenceError: Cannot access 'y' before initialization
      let y = 5;
      
  • Function Hoisting:

    • Function declarations are fully hoisted to the top of their scope.
    • Function expressions are not hoisted completely like function declarations.
    • Example:
      greet(); // Outputs: "Hello!"
      
      function greet() {
          console.log("Hello!");
      }
      
      sayHi(); // TypeError: sayHi is not a function
      var sayHi = function() {
          console.log("Hi There!");
      };
      

Closures

A Closure is a term used to describe the environment in which an inner function is declared. It grants the inner function access to variables in its outer (enclosing) function's scope, as well as to global variables and functions, even after the outer function has finished execution. Closures are powerful constructs that make JavaScript a versatile language for both functional and object-oriented programming.

  • Creation of a Closure:

    • When a function is defined inside another function and accesses variables from the outer function, a closure is created.
    • The inner function retains a reference to the outer function's scope even when the outer function is complete.
    • Example:
      function createClosure(name) {
          return function() {
              console.log("Hello, " + name);
          };
      }
      
      var greetAlice = createClosure("Alice");
      greetAlice(); // Outputs: "Hello, Alice"
      
  • Practical Applications:

    • Data hiding and encapsulation.
    • Function factories.
    • Private methods in classes.
    • Asynchronous programming (callbacks, promise chains).
    • Decorators.
    • Caching/memoization.
  • Limitations / Considerations:

    • Memory leaks: Not properly managing closures can result in memory leaks as the engine doesn't garbage collect the outer environment.
    • Debugging challenges: Can make it harder to understand variable lifecycles.
    • Performance considerations: Depending on the implementation, closures can carry performance costs.

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement JavaScript Scope, Hoisting, and Closures

JavaScript Scope

Scope in JavaScript defines where variables and functions are accessible. There are two main types of scope: Global Scope and Local Scope.

Example 1: Global Scope

// Declare a variable in the global scope
let username = "JohnDoe";

function printUsername() {
    console.log(username); // Accessing global variable inside function
}

printUsername(); // Output: JohnDoe
console.log(username); // Output: JohnDoe -> Accessing global variable outside function

In this example, username is declared outside any function, making it globally accessible from anywhere in the code, including within the printUsername function.

Example 2: Local Scope

function greetUser() {
    let message = "Hello, User!";
    console.log(message); // Accessing local variable inside function
}

greetUser(); // Output: Hello, User!
console.log(message); // Error: Uncaught ReferenceError: message is not defined
// The 'message' variable cannot be accessed here because it's locally scoped.

In this example, message is declared within the greetUser function and thus only accessible within that function.

Nested Scopes:

Variables in an outer (parent) function can be accessed by inner functions.

function createUser() {
    let name = "Alice";
    
    function displayName() {
        console.log(name); // Accessing parent's variable name
    }

    displayName(); // Output: Alice
}

createUser();
console.log(name); // Error: Uncaught ReferenceError: name is not defined
// The 'name' variable is not accessible here because it's in the createUser function's local scope.

JavaScript Hoisting

Hoisting refers to the default behavior of moving all declarations to the top of the current scope before code execution. Variable declarations and function declarations are hoisted, but not initializations.

Example 1: Hoisting with Variables

console.log(x); // Output: undefined
var x = 5;
console.log(x); // Output: 5

// Equivalent hoisting
var x;
console.log(x); // Output: undefined
x = 5;
console.log(x); // Output: 5

Here, the declaration var x; is moved to the top of the script, even though technically it was declared later. That’s why we get undefined when printing x immediately after logging it before assigning its value.

Example 2: Hoisting with Functions

Functions are hoisted entirely in JavaScript.

helloWorld(); // Output: Hello, World!

function helloWorld() {
    console.log("Hello, World!");
}

// Equivalent hoisting
function helloWorld() {
    console.log("Hello, World!");
}
helloWorld(); // Output: Hello, World!

The entire function helloWorld is hoisted to the top, allowing us to call it even before its definition in the code.

Hoisting with let, const, and class:

  • let and const declarations are hoisted but accessing variables before initialization within their block scope will result in a ReferenceError. This phenomenon is called the temporal dead zone (TDZ).
  • class declarations have similar behavior to let and const - you cannot access them before they've been declared.
// Using let
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
console.log(y); // Output: 10

// Using const
console.log(z); // ReferenceError: Cannot access 'z' before initialization
const z = 20;
console.log(z); // Output: 20

// Using class
console.log(MyClass); // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {}
console.log(MyClass); // [Function: MyClass]

JavaScript Closures

A closure is a function having access to the parent scope, even after the parent function has closed. It “closes over” the variables available to it at the time of its creation.

Example 1: Basic Closure

function makeCounter() {
    let count = 0; // Parent function scope variable

    function counter() {
        // Increment parent function's variable and return it.
        count += 1;
        return count;
    }
    
    return counter; // Returning the inner (child) function
}

const myCounter = makeCounter(); 
console.log(myCounter()); // Output: 1
console.log(myCounter()); // Output: 2
console.log(myCounter()); // Output: 3

// Even after the makeCounter() execution completes,
// myCounter holds the reference to the 'count' variable from parent function's scope.

Example 2: Practical Use of Closure (Private Data)

function createEmployee(name) {
    let privateSalary = 0;

    return {
        setName: function(newName){
            name = newName;
        },

        getName: function(){
            return name;
        },

        getSalary: function(){
            return privateSalary;
        },

        setSalary: function(salary){
            privateSalary = salary;
        },

        calculateAnnualBonus: function(bonusPercentage){
            return (privateSalary * bonusPercentage) / 100;
        }
    };
}

const emp = createEmployee('Bob');
emp.setSalary(50000);
console.log(emp.getSalary()); // Output: 50000
console.log(emp.calculateAnnualBonus(10)); // Output: 5000

emp.privateSalary = 75000; // Trying to directly manipulate private data
console.log(emp.getSalary()); // Still Output: 50000 -> Private data remains unchanged.

In this example, createEmployee returns an object with some public methods (setName, getName, setSalary, getSalary, calculateAnnualBonus). These methods can manipulate the private name and privateSalary variables from the createEmployee function's scope even after createEmployee itself is no longer running.

Summary

  • Scope: Determines where variables and functions can be accessed; global or local.
  • Hoisting: Moves variable and function declarations to the top of their scope before execution; initializations stay in place.
  • Closures: A function remembers and retains access to variables from its containing (outer/parent) scope, even after it leaves the scope in which it was declared.

Top 10 Interview Questions & Answers on JavaScript Scope, Hoisting, and Closures

1. What is JavaScript Scope?

Answer: Scope in JavaScript refers to the visibility and lifetime of variables and functions. JavaScript has two main types of scope:

  • Global Scope: Variables declared outside of a function are global and can be accessed from anywhere in the code.
  • Local Scope: Variables declared inside a function are local and can only be accessed within that function.

2. What is Hoisting in JavaScript?

Answer: Hoisting is a JavaScript behavior where variable and function declarations are moved to the top of their containing scope during the compile phase, before the code is executed. This means that you can use a variable or function before it has been declared.

  • Only declarations (e.g., var, function) are hoisted, not initializations.
  • let and const declarations are hoisted, but they remain uninitialized in a "temporal dead zone" until their declaration is processed.

3. What is the difference between var, let, and const in terms of scope?

Answer:

  • var: Function-scoped or globally-scoped. Variables declared with var can be redeclared and reassigned.
  • let: Block-scoped. Variables declared with let can be reassigned but not redeclared in the same scope.
  • const: Block-scoped and immutable (cannot be reassigned). However, if the variable references an object or array, the contents of that object or array can still be modified.

4. How does hoisting affect functions and function expressions?

Answer:

  • Function Declarations: Entire function is hoisted. You can call the function both before and after its declaration.
  • Function Expressions: Only the variable declaration is hoisted, not the function assignment. Invoking the function before the assignment will result in a TypeError.

5. What are closures in JavaScript?

Answer: A closure is a function that has access to its outer function's scope, even after the outer function has returned. This allows the inner function to "remember" and access variables from the outer function's scope, enabling data encapsulation and private state.

function createCounter() {
    let count = 0;
    return function incrementCounter() {
        count++;
        return count;
    }
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

Here, incrementCounter forms a closure that captures the count variable.

6. Can you explain scope chain in JavaScript?

Answer: The Scope Chain is a system used by JavaScript to resolve variables and functions when a particular variable is referenced. When a variable is searched for, the search begins in the current scope and continues up to the global scope, following the scope chain. If the variable is not found, a ReferenceError is thrown.

7. Is it possible to create a closure unintentionally?

Answer: Yes, closures can be created unintentionally if an inner function references variables from its outer function's scope without the intent to create a private variable or function. Ensure that any function that references outer variables is intended to be a closure.

8. How does the this keyword work with closures?

Answer: Inside a closure, the value of this depends on the context in which the function is called, not where it is defined. If you need this to point to a specific context, use techniques like .bind(), .call(), or arrow functions to ensure the correct context.

9. What are the benefits of using closures in JavaScript?

Answer: Closures provide several benefits:

  • Encapsulation: Hide data and expose only necessary functionality (public methods).
  • Data Privacy: Prevent direct modification of variables.
  • Factory Functions: Create unique instances of functions with private state.
  • Memory Management: Efficient use of resources by managing state without requiring global variables.

10. Can closures lead to memory leaks in JavaScript?

Answer: Yes, closures can potentially cause memory leaks if not managed carefully. When closures capture external variables, those variables are not garbage collected even if they are no longer needed. To prevent memory leaks, ensure that closures do not inadvertently hold references to objects that are no longer needed, and release those references when they are no longer required. Using modern JavaScript features and garbage collection best practices can mitigate this issue.

You May Like This Related .NET Topic

Login to post a comment.