> For the complete documentation index, see [llms.txt](https://mihai-gheorghe.gitbook.io/tic/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mihai-gheorghe.gitbook.io/tic/advanced-javascript/runtime-engine-callstack.md).

# Engine, Call Stack, Scope and Closures

## JIT compilers

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

![How V8 runs a function: parsing, bytecode, interpretation and optimization](https://1172597814-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MJ6Mj8gFbz9Ji6QL6Zi%2Fuploads%2Fgit-blob-f85ebb8f6ad0c39ee956a8d5d98b572aaaefa8de%2Fjs-jit-pipeline.svg?alt=media)

## Execution context and the call stack

Each function call gets an **execution context**, which holds the function's arguments, its local variables and the value of `this`. The engine keeps these contexts on the **call stack**, which is LIFO (last in, first out): a call pushes a new context on top, and returning pops it.

```javascript
function multiply(a, b) {
  return a * b;
}

function square(n) {
  return multiply(n, n);
}

function printSquare(n) {
  console.log(square(n));
}

printSquare(4); // 16
// the stack grows: printSquare → square → multiply, then shrinks back
```

While `multiply()` runs, the stack holds, from top to bottom, `multiply`, `square`, `printSquare` and the global execution context, created for the script itself. The engine runs on a single thread, one function at a time: synchronous code runs to completion, and nothing else can run until the stack is empty. [Asynchronous JavaScript](/tic/advanced-javascript/asynchronous-javascript.md) shows how the runtime works around this.

## Hoisting

When an execution context is created, before any of its code runs, the engine registers the **declarations** it contains. This is called **hoisting**, because declarations behave as if they were moved to the top of their scope:

* **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.

## Scope and 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-x-prod.appspot.com/o/spaces%2F-MJ6Mj8gFbz9Ji6QL6Zi%2Fuploads%2Fgit-blob-4cacd5ab028c244764aea160ed0028c44bf73201%2Fjs-scope-chain.svg?alt=media)

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.

### Hoisting meets scope

This classic example combines shadowing and `var` hoisting inside a function scope. What does the first `console.log` print?

```javascript
var favouriteFood = "grapes";

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

foodThoughts();
```

<details>

<summary>Output</summary>

`Original favourite food: undefined`, then `New favourite food: sushi`. The inner `var favouriteFood` is hoisted (set to `undefined`) and **shadows** the outer one until assignment.

</details>

## Closures

A function keeps access to the variables of the scope in which it was created, even after that scope has finished running. The function together with these remembered variables is called a **closure**.

```javascript
function createCounter() {
  let count = 0;            // local variable of createCounter
  return function () {
    count++;                // still accessible after createCounter has returned
    return count;
  };
}

const counter = createCounter();
counter(); // 1
counter(); // 2
// count cannot be read or changed from outside: only through counter()
```

Closures keep data private (encapsulation, see [OOP in JavaScript](/tic/advanced-javascript/oop-javascript.md)) and let a function remember results between calls (memoization, see [Functional Programming](/tic/advanced-javascript/functional-programming.md#memoization)).

## Takeaways

* The call stack holds one execution context per running function; its declarations are hoisted when it is created, so declare before use.
* Scope is lexical; inner scopes shadow outer bindings. Keep variable lifetimes tight (block scope) to reduce bugs.
* A closure lets a function keep using the variables of the scope where it was created, after that scope has finished running.
