# Hoisting and scoping: A deep dive into your interviewer's favourite question.

If you have ever given a javascript interview chances are you have been asked about hoisting and variable scoping. You may know the definition but in order to solve those tricky "guess the output" questions you need to have a deep knowledge about how javascript works under the hood. Which is precisely what I am going to cover in this article. So put on your reading glasses, put away your phone and get ready to dive deep, unless of course you are reading this on your phone... don't put away your phone it that case ✌️. But if you are here just for the interview questions then skip to the end and go to town! Anyways let's begin.

### Declaring a variable in javascript

Just in case you don't already know there are three ways of declaring a variable in javascript know to mankind (bots too these days 🤖): var, let and const.

**var**

Ah, `var`, a relic of JavaScript's earlier days, presumably crafted by developers hammering away on Windows XP using Notepad as their trusty sidekick. It's notorious for turning straightforward code into a head-scratching puzzle.

Variables declared with `var` can be both re-assigned and re-declared, setting the stage for some truly perplexing bugs. 😬

**let**

The cooler, younger sibling introduced in ES6. Variables declared with `let` can be reassigned but not re-declared within the same scope. It's not necessary to initialise them at the point of declaration.

**const**

Also debuting in ES6, `const` is for constants. Reassigning them will throw an error. You must initialise `const` variables when you declare them.

### **Variable Scope**

Scope defines where variables can be accessed within your code. If you declare a variable inside a function, it's only accessible within that function, not outside. Scope comes in three flavours: global, function, and block.

```javascript
function daScope() {
  const fullName = "Eren Yeager";
  console.log(fullName); // Outputs: Eren Yeager

  if (true) {
    const titan = "mix veg";
    console.log(titan); // Outputs: mix veg
  }
  
  console.log(titan); // Error: titan is not defined
}

daScope();
```

This code neatly illustrates block scoping. However, `var` plays by its own rules, it's function-scoped, so it can be accessed anywhere within the function.

```javascript
function daScope() {
  const fullName = "Eren Yeager";
  console.log(fullName); // Outputs: Eren Yeager

  if (true) {
    var titan = "mix veg";
    console.log(titan); // Outputs: mix veg
  }
  
  console.log(titan); // Outputs: mix veg, no error!
}

daScope();
```

Think that’s quirky? Check out this scenario:

```javascript
function greetingCreator() {
    var greeting = "Hello";

    if (true) {
        var greeting = "Hi";  // This redeclares and reassigns `greeting` 🤯  
        console.log(greeting); // Output: "Hi"
    }

    function displayGreeting() {
        console.log(greeting);
    }

    displayGreeting(); // Output: "Hi"
}

greetingCreator();
```

Both outputs are "Hi", demonstrating that `var` does not recognise block scope, but only function scope. The `greeting` inside the `if` block affects the same `greeting` declared at the function's start.

### **Variable Shadowing**

To avoid the chaos of `var`, we use `let`, which enables variable shadowing. Here, if a variable declared in a nested scope has the same name as one in an outer scope, it shadows the outer variable without affecting it.

```javascript
function greetingCreator() {
    let greeting = "Hello";

    if (true) {
        let greeting = "Hi"; // A new, shadowed `greeting`
        console.log(greeting); // Output: "Hi"
    }

    function displayGreeting() {
        console.log(greeting); 
    }

    displayGreeting(); // Output: "Hello"
}

greetingCreator();
```

**Illegal Shadowing**

And then there’s illegal shadowing, brought to you by `var`, of course:

```javascript
function greetingCreator() {
    var greeting1 = "Konichiwa";
    let greeting2 = "Hello";

    if (true) {
        let greeting1 = "Hajime Mashite"; // This is fine
        console.log(greeting1); // Output: "Hajime Mashite"

        var greeting2 = "Hi"; // Error: Identifier 'greeting2' has already been declared 
        console.log(greeting2);  
    }

    function displayGreeting() {
        console.log(greeting2); 
    }

    displayGreeting();  
}

greetingCreator();
```

### Javascript Execution Context

JavaScript's execution context is one of the most fundamental concepts to grasp to understand how code executes, particularly how scopes, hoisting, closures, and asynchronous callbacks work. Let's take a detailed look at how JavaScript handles execution context through a step-by-step example.

Consider this JavaScript code snippet:

```javascript
var x = 10;
let y = 20;

function multiply() {
    var z = x * y;
    console.log(z);
    return z;
}

function display() {
    let a = 5;
    console.log(a);
    multiply();
}

display();
console.log(x);
```

Here’s how JavaScript processes this code:

When the script loads, JavaScript creates a Global Execution Context. This global context performs two main actions during the creation phase:

* **Variable Environment Creation**: Here, all the variable and function declarations are hoisted. Variables declared with `var` are initialised to `undefined`, and functions are hoisted with their definitions. Variables declared with `let` and `const` remain uninitialised at this point and are in a temporal dead zone.
    
* **Scope Chain Establishment**: Sets up the scope chain, which determines the variable access throughout the code.
    
* **This Value Determination**: For global execution context, `this` refers to the global object (`window` in browsers, `global` in Node.js).
    

After hoisting, the environment looks something like:

```javascript
// Global Execution Context (GEC) starts

// Hoisted during the creation phase of GEC
var x = undefined; // 'var' variables are initialized as undefined
let y;             // 'let' and 'const' are in Temporal Dead Zone (TDZ) and not initialized
function multiply() { /* function body is fully hoisted */ }
function display() { /* function body is fully hoisted */ }

// Execution phase of GEC begins
x = 10; // 'x' is now assigned a value
y = 20; // 'y' is assigned a value and comes out of TDZ

function display() {
    // New Function Execution Context for display
    let a = 5; // Local 'let' declaration, only exists within display
    console.log(a); // Logs 5
    multiply();     // Calls multiply, creating a new context for multiply
}

function multiply() {
    // New Function Execution Context for multiply
    var z = x * y;  // 'var' declared and calculated using global x and y
    console.log(z); // Logs 200
    return z;       // Returns 200, multiply context will be popped off the stack after return
}

display();          // Calls display, logs 5, then 200 from multiply
console.log(x);     // Back in GEC, logs 10

// Both multiply and display Function Execution Contexts have been popped off
// Back in the GEC until script ends, at which point GEC is also remove
```

* **Global Execution Context Setup**: At the start, JavaScript engine hoists function declarations and `var` variables in the global scope.
    
* **Function Calls & Scope**: When `display` and `multiply` functions are called, they each create their own execution contexts.
    
    * `display` Function Context: Contains its own local variables (e.g., `a`) and accesses functions and variables from the global scope.
        
    * `multiply` Function Context: Accesses global variables (`x` and `y`) and has its own local variable (`z`).
        
* **Execution Flow**: After `display` calls `multiply`, `multiply` computes a result and finishes, popping its context off the stack. Then `display` finishes, returning control to the global context.
    
* **End of Execution**: After all function contexts resolve, and the script completes, the global context is finally popped off the execution stack.
    

### Hoisting

**Hoisting** is JavaScript's behaviour of moving variable and function declarations to the top of their scope during the compilation phase, before any code is executed. This means that declarations are processed before any line of code runs, giving the illusion that they are "hoisted" to the top.

Imagine you walk into a classroom and see the teacher’s notes already written on the board. You didn’t see her write them, but somehow they were there before the class even started. That’s hoisting in JavaScript — the interpreter sneakily moves declarations to the top of their scope before any code is run.

But there’s a twist.

Hoisting doesn’t actually move your code physically. What it does is register certain declarations, just the declarations, not the initialisations, during the compilation phase, before your code runs. So when execution begins, JavaScript already knows about the existence of variables and functions, it just might not know their values yet.

Let’s break it down.

#### `var` Hoisting

```javascript
console.log(hoistedVar); // undefined
var hoistedVar = 42;
```

You might expect this to throw an error, but nope, it prints `undefined`. Why? Because `var hoistedVar` is hoisted to the top like this:

```javascript
var hoistedVar;       // Declaration is hoisted
console.log(hoistedVar); // undefined (default value for hoisted `var`)
hoistedVar = 42;      // Initialization stays where it is
```

That’s classic `var` behaviour. Its declarations are hoisted and automatically initialised with `undefined`.

#### Function Hoisting

```javascript
greet(); // "Hello!"

function greet() {
  console.log("Hello!");
}
```

This works beautifully because function declarations are fully hoisted, both the name and the body. Think of it like JavaScript giving your functions a VIP backstage pass. They’re not just invited early, they come fully dressed and ready to perform.

But here's a curveball:

```javascript
greet(); // TypeError: greet is not a function

var greet = function () {
  console.log("Hi!");
};
```

Even though `greet` is declared with `var`, it’s hoisted as a variable, not as a function. So what gets hoisted?

```javascript
var greet;     // Only the declaration is hoisted
greet();       // greet is undefined at this point → TypeError
greet = function () {
  console.log("Hi!");
};
```

This is why function declarations and function expressions behave differently when it comes to hoisting.

Now enter `let` and `const`...

You might think `let` and `const` are also hoisted. Technically, they are. But not in the way you'd hope.

```javascript
console.log(a); // ❌ ReferenceError
let a = 10;
```

Even though the `let a` declaration is hoisted, it's not accessible until the line where it's declared. The period between the start of the scope and the actual declaration is known as the **Temporal Dead Zone (TDZ)**.

Variables in the TDZ cannot be accessed, not even to check if they exist.

```javascript
if (true) {
  console.log(name); // ❌ ReferenceError
  let name = "Zoro";
}
```

Try to sneak a peek at `name` before its declaration, and JavaScript slaps you with:

**ReferenceError. But... why?**

Well, welcome to the Temporal Dead Zone.

### Temporal Dead Zone (TDZ)

Sounds scary, right? Like a place in a sci-fi movie where time and logic cease to exist. And honestly, that’s not far off.

The **Temporal Dead Zone** is the time between a variable being hoisted and being initialised, where accessing it will throw an error.

Here’s the deal:

* `let` and `const` **are hoisted**, but unlike `var`, they are **not initialised with** `undefined`.
    
* Until the line where they are declared is actually executed, they're in the **TDZ,** a forbidden zone.
    
* If you try to access them in that zone, JavaScript throws a **ReferenceError**, basically saying:  
    *“Hold your horses! You can’t use this yet.”*
    

Let’s break it down:

```javascript
function sayHi() {
  console.log(name); // 🚨 ReferenceError
  let name = "Zoro";
}
sayHi();
```

Why does this error happen?

Even though `name` is hoisted to the top of the function’s scope, it’s in the **TDZ** from the beginning of the scope until the line `let name = "Zoro";` is actually run.

Now compare this to `var`:

```javascript
function sayHi() {
  console.log(name); // undefined
  var name = "Zoro";
}
sayHi();
```

This time you get `undefined`, because `var` is hoisted and initialised with `undefined`. Not smarter, just sneakier.

### The TDZ is Actually a Good Thing

Yes, I said it. The TDZ is your friend. It exists to **prevent weird bugs** and **enforce better coding practices**. If a variable is in the TDZ, it means:  
*“This thing has not been safely initialised yet, don't touch it.”*

Without the TDZ, it would be a lot easier to write confusing or broken code. So, while it may feel like JavaScript is being overly strict, it’s actually trying to help you out.

### Hoisting interview questions

### 1\. **Why doesn’t this function run as expected?**

```javascript
console.log(foo());
var foo = function () {
  return "done";
};
```

**Output:**  
`TypeError: foo is not a function`

**What’s wrong here?**  
The `var foo` is hoisted, but its value is set to `undefined` at the top. So when `foo()` is called, it's actually calling `undefined()`.

**How to fix it?**  
Use a function declaration instead of a function expression if you need to call the function before it's defined:

```javascript
function foo() {
  return "done";
}
console.log(foo());
```

Or, move the function expression below the `console.log` if you're using modern patterns like `const` or `let`.

### 2\. **What’s happening here with** `let` and shadowing?

```javascript
let a = 10;

function test() {
  console.log(a);
  let a = 20;
}
test();
```

**Output:**  
`ReferenceError`

**What’s wrong here?**  
At first glance, you might think `a` will be `10`. But inside the function, `let a` creates a new `a` in the block scope, and it’s in the **Temporal Dead Zone** when `console.log(a)` runs.

**How to fix it?**  
If you want to access the outer `a`, don't redeclare it with `let` inside the function:

```javascript
let a = 10;

function test() {
  console.log(a); // 10
}
test();
```

If you need a new `a`, define it after you've logged the outer one:

```javascript
function test() {
  console.log(a); // 10
  let aNew = 20;
}
```

### 3\. **What’s going on with these** `var` declarations?

```javascript
var count = 5;

(function () {
  if (false) {
    var count = 10;
  }
  console.log(count);
})();
```

**Output:**  
`undefined`

**What’s wrong here?**  
You might expect `count` to be `5`, but the inner `var count` is hoisted to the top of the IIFE and initialised as `undefined`, even though the `if` block never runs.

So inside the function, `count` is `undefined` due to hoisting, not `5` from the outer scope.

**How to fix it?**  
Use `let` or `const` to keep block scoping:

```javascript
var count = 5;

(function () {
  if (false) {
    let count = 10;
  }
  console.log(count); // 5
})();
```

Or rename variables to avoid this subtle collision.

### 4\. **Why is this loop printing unexpected values?**

```javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
```

**Output:**  
`3`  
`3`  
`3`

**What’s wrong here?**  
The `var i` is function-scoped, not block-scoped. So all three callbacks share the same `i` and print its final value after the loop ends.

**How to fix it?**

Use `let` for block scoping:

```javascript
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
```

Or use an IIFE to capture the current `i`:

```javascript
for (var i = 0; i < 3; i++) {
  (function (j) {
    setTimeout(() => console.log(j), 100);
  })(i);
}
```

### 5\. **Why does this throw when you access** `foo` inside `bar`?

```javascript
function foo() {
  console.log("outer foo");
}

function bar() {
  console.log(foo);
  function foo() {
    console.log("inner foo");
  }
}
bar();
```

**Output:**  
`[Function: foo]`

Wait... it doesn’t throw?

Actually no, **but here's the twist**, this often catches people off-guard.

**What’s really happening?**  
The function `foo` inside `bar` is hoisted to the top of `bar`'s scope. So when `console.log(foo)` runs, it’s actually logging the function itself, not calling the outer `foo`.

**Follow-up:**  
Change `console.log(foo)` to `foo()` what happens?

```javascript
function bar() {
  foo(); // this calls the inner foo, not the outer one
  function foo() {
    console.log("inner foo");
  }
}
bar();
```

The output is `"inner foo"` because the inner declaration overshadows the outer one.

**Fix (if you want the outer one):**  
Rename the inner function or avoid redeclaring functions with the same name.

### 6\. **Why does this function behave differently based on where it’s declared?**

```javascript
"use strict";

if (true) {
  function test() {
    console.log("block");
  }
}
test();
```

**Output:**  
In some environments: `ReferenceError`

**Why is this tricky?**  
Function declarations inside blocks are a grey area in JavaScript. In strict mode, function declarations are not block-scoped in older engines, and behaviour can vary between browsers.

In modern JavaScript, this should throw a ReferenceError, because `test` is scoped only inside the `if` block.

**How to fix it?**  
Use function expressions or `const` if declaring inside a block:

```javascript
if (true) {
  const test = () => console.log("block");
  test();
}
```

Or, declare it outside the block if it needs wider visibility.

### That’s a Wrap

I know you are having a blast but the show must end here, I have to feed my pet (virtual pet 🫠). On the bright side you can now tackle any hoisting and scoping related interview questions like a pro. Next time we meet, I’ll let you in on another JavaScript mystery. Spoiler alert: **PROMISES**!
