# Runtime Engine, Callstack, Scope

## Runtime Engine, Call Stack, Scope

### JIT compilers

Modern JS engines (V8, JavaScriptCore, SpiderMonkey) parse your source into an AST, generate bytecode for a fast interpreter, then—based on runtime profiling—optimize hot paths with a JIT compiler. This mixed strategy balances quick startup with high peak performance.

![V8 JS engine compiling example](https://1172597814-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJ6Mj8gFbz9Ji6QL6Zi%2F-MKJnu4wgOGihR_niiXU%2F-MKK6dEdmR7hPJhO3HVM%2Fbytecode.svg?alt=media\&token=1baa00e2-7dd2-4e0a-8237-734861add706)

#### Hoisting (what actually happens)

Before executing your code, the engine creates **execution contexts** and their **environment records**. During this compile/setup phase, **declarations** are registered:

* **Function declarations** are hoisted with their full definition and can be called earlier in the file.
* **`var` declarations** are hoisted and initialized to `undefined` (the assignment happens later).
* **`let` / `const` declarations** are hoisted too, but remain in the **Temporal Dead Zone (TDZ)** until their declaration line runs—accessing them earlier throws a `ReferenceError`.
* **Function expressions** are *not* hoisted as callable functions; only the variable binding (e.g., with `var`) is hoisted.

```javascript
logToConsole();            // ✅ "Hello"
console.log(x);            // ✅ undefined (var is hoisted, assigned later)

function logToConsole() {
  console.log("Hello");
}

var x = 5;
```

> Common fix: don’t rely on hoisting for readability—declare before use.

***

### JavaScript runtimes (quick refresher)

A **runtime** = engine + Web/Host APIs + queues + event loop. The engine is single-threaded, but the runtime exposes asynchronous primitives (timers, network, DOM events) that schedule work back to JS.

![](https://1172597814-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJ6Mj8gFbz9Ji6QL6Zi%2F-MKJkhamQI4owCk2KpIA%2F-MKJnsxRbic184Wj9Ot1%2FJS%20runtime%20engine.png?alt=media\&token=72d456ae-cea3-4dd6-86ef-ef57ec9f237f)

***

### Execution context & the Call Stack

* The **Call Stack** is LIFO: a function call pushes a frame; returning pops it.
* “Blocking” synchronous code runs to completion before the runtime can process queued callbacks/microtasks.

```javascript
// Inspect order with DevTools
debugger; // step to observe the stack frames

console.log("1");
setTimeout(function () {
  console.log("2");
}, 3000);
console.log("3");
console.log("4");
console.log("5");
setTimeout(function () {
  console.log("6");
}, 0);
console.log("7");
console.log("8");
console.log("9");
```

#### Microtasks vs (macro)tasks

* **Microtask queue** runs **after** the current stack frame, **before** any timer/IO callbacks: Promise reactions, `MutationObserver`, queueMicrotask.
* **Task/callback queue** contains timers, IO, UI events, etc.\
  Result: Promise handlers run before `setTimeout(fn, 0)` callbacks.

```javascript
console.log("Start");

setTimeout(function () {
  console.log("Timeout callback");
}, 0);

Promise.resolve().then(function () {
  console.log("Promise microtask");
});

console.log("End");

// Output:
// Start
// End
// Promise microtask
// Timeout callback
```

> Note: `fetch()` completes in the host environment and resolves a **Promise**; its `.then/.catch` handlers run as **microtasks**.

{% hint style="danger" %}
In the JS runtime, the **microtask queue** has higher priority and is drained **immediately after the current call stack** (and before rendering), while the **callback (task/macro-task) queue** runs **after all pending microtasks** and typically yields to rendering between tasks; examples of **microtasks** include Promise reactions (`promise.then/catch/finally`), `queueMicrotask`, and `MutationObserver`, so a **network request with `fetch`** resolves to a Promise whose handlers execute as microtasks, often ahead of timers; by contrast, the **callback queue** holds tasks like `setTimeout`/`setInterval`, `MessageChannel`/`postMessage`, user **DOM events** (e.g., `click`, `input`, `submit`), and certain **network/DOM load events** like `img.onload` or `XMLHttpRequest.onload`, which will run only after the microtask queue has been fully emptied.
{% endhint %}

***

### Scope & the Scope Chain

**Scope** is where identifiers are visible. Child scopes can read parent bindings (via the **scope chain**), not vice-versa. JavaScript uses **lexical (static) scoping**: visibility is determined by where code is written.

```javascript
var x = "declared outside function";

exampleFunction();

function exampleFunction() {
  console.log("Inside function");
  console.log(x); // reads from outer scope
}
```

![Example of the scope chain](https://1172597814-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJ6Mj8gFbz9Ji6QL6Zi%2F-MKKOFHPdUCP2WmUhYw5%2F-MKKOSjup6K5rrckJJ2t%2F1_BwARRnm0-gFoh-Rq_ubbwQ.png?alt=media\&token=3ebc3e8d-f4da-41a7-9dff-20603eeee0db)

#### Hoisting meets scope

This classic example shows shadowing + `var` hoisting inside a function scope: the inner `var favouriteFood` is hoisted (set to `undefined`) and **shadows** the outer one until assignment.

```javascript
var favouriteFood = "grapes";

var foodThoughts = function () {
  console.log("Original favourite food: " + favouriteFood);
  var favouriteFood = "sushi";
  console.log("New favourite food: " + favouriteFood);
};

foodThoughts();

// Output:
// Original favourite food: undefined   <-- inner var is hoisted but uninitialized
// New favourite food: sushi
```

Additional notes:

* **Function parameters** are local to the function body.
* **Block scope** (`let`/`const`) confines bindings to `{ ... }` blocks and prevents use before initialization (TDZ). Prefer `const`, use `let` only when you must reassign; avoid `var` in modern code.

***

### Quick takeaways

* Don’t depend on hoisting for clarity—declare before use.
* Understand the event loop order: **stack → microtasks → tasks**.
* Scope is lexical; inner scopes shadow outer bindings. Keep variable lifetimes tight (block scope) to reduce bugs.
