● Engineering Deep Dive
JavaScript
Scope, Execution
Context & Closures
How the engine decides what your code can see — and why it matters for every pattern you write.
🏷️
Identifiers & Scope
Names, variables, and where they live — set at parse time, not runtime
⚙️
Execution Context
How JS sets up memory before running a single line of code
🔒
Closures
Functions that remember their environment — the most powerful pattern in JS
Real-world Usage
React hooks, async traps, debounce, module patterns
Agenda
What we'll cover
01
Identifier & Scope Definition
What gets named?
02
JS Engine Overview
V8 pipeline & engines
03
Parsing, Tokens & AST
How code is read
04
How Scope Is Created
During parsing, before run
05
Lexical Scope
Write-time rules
06
Scope Types
Global · Function · Block · Module
07
Scope Chain & Shadowing
Lookup & override
08
Execution Context
Interactive demo
09
Hoisting
var vs let/const
10
Temporal Dead Zone
let/const before init
11
Closures
Scope that persists
12
Async, Loop Traps & Real-world
Pitfalls & patterns
Topic 01
Identifier
Everything that has a name in your program
Scope = the rules that determine which identifiers are visible and accessible at any given point in your code.
What is an identifier?
  • A name that refers to something — variable, function, class, parameter, label
  • Every identifier lives in exactly one scope
  • The engine resolves identifiers at runtime by walking the scope chain
Topic 02
JavaScript Engine Overview
How JS engines turn source text into running code
Major engines
Engine Used by Key tech
V8 Chrome, Node.js, Deno Ignition (interpreter) + TurboFan JIT (compiler)
SpiderMonkey Firefox IonMonkey JIT (compiles hot paths to machine code)
V8 pipeline — source to machine code
📄
Source
JS text
🔍
Parser
tokens → AST
⚛️
Ignition
bytecode
📈
Profiler
hot paths
TurboFan
JIT machine code
Key insight: Scope is resolved during the Parser phase — long before Ignition runs a single instruction.
Topic 03
Parsing, Tokens & AST
Before any code runs, the engine reads every character and builds a structured map of your program
1
Source Code
const x = 10; function greet(name) { let msg = "hi" return msg }

Raw text — the engine doesn't know keywords from names yet.

2
Tokenisation
KEYWORD
const
·
IDENTIFIER
x
·
OPERATOR
=
·
NUMBER
10
·
PUNCTUATOR
;
✓ whitespace ignored
✓ comments stripped
✓ each token has type + value

Each character group gets a type — keyword, identifier, operator, number.

3
Parser → AST
Program
VariableDeclaration
const x = 10;
VariableDeclarator
x = 10
Identifier
x
NumericLiteral
10

Tokens become a tree. function and {} nodes mark scope boundaries.

📌 Scope is decided here
Every function and {…} locks in scope — before execution.
📌 Identifiers are registered
All declarations are recorded in their scope — this is what makes hoisting possible.
📌 Lexical — not dynamic
Scope is set from the source text — where you write it matters, not where you call it.
Topic 04
How Scope Is Created During Parsing
Scope is decided before the first line of code executes
Parser discovers scope boundaries
Global Scope
env: "production"
Function Scope — greet()
name: param
Block Scope — if block
msg: let
Source that creates the scope above
Each function and {} block opens a new scope — registered at parse time, before any code runs.
Topic 05
Lexical Scope
Scope is determined by where you write code, not where you call it
Example
Mental model: draw boxes around each function body. Inner boxes can see outward — outer boxes can never see inward.
Scope boundaries as nested boxes
Global
visible: outer
outer()
visible: a, outer
inner()
visible: b, a, outer
Key rules
  • Scope is set at parse time — calling inner() from anywhere gives the same scope
  • A function always has access to its definition-site scope
  • This is what makes closures possible
Topic 06
Scope Types
Four containers where identifiers can live
🌎 Global scope
Accessible everywhere. Avoid polluting it.
📚 Function scope
var leaks out of blocks but NOT out of functions.
👄 Block scope ES6
let / const are confined to the nearest {}.
Topic 07
Scope Chain & Shadowing
When a variable is used, the engine walks up the chain of scopes until it finds it — or throws a ReferenceError
Scope Chain
Code — where is x declared?
Lookup walk — resolving x inside inner()
1
inner()
✗ not found
inner() only has its own local variables. No x here — engine looks up.
2
outer()
✗ not found
outer() also has no x. Engine continues up the chain.
3
global
✓ found x = 1
x is declared at global scope. Value 1 is returned. If not found anywhere → ReferenceError.
Shadowing
Same name, different scope
GLOBAL SCOPE
color = "blue"
Untouched. Still "blue" after paint() returns.
paint() SCOPE
color = "red"
A new binding. Shadows the global but doesn't overwrite it.
Key rules
Engine always starts at the innermost scope and walks outward — never inward.
Shadowing hides the outer variable — it does not mutate it.
If no scope in the chain has the name → ReferenceError: x is not defined.
The chain is fixed at parse time — it never changes at runtime.
Topic 08
Execution Context
The private environment JavaScript builds every time a function is called
Scope is the rule — fixed at write time.   Execution context is the room — built fresh at call time.
The scope chain lives inside every execution context — that's how a function looks up variables from outer scopes.
① Creation Phase
  • Memory allocated for variables & functions
  • Scope chain wired up from lexical position
  • varundefined  ·  functions → fully hoisted
② Execution Phase
  • Code runs line by line
  • Variables receive their actual values
  • Each nested call pushes a new context onto the call stack
Why this matters for scope
  • Scope chain is built from where the function was written, not where it was called
  • Context is destroyed when the call ends — unless a closure keeps the scope chain alive
Interactive Demo
Execution Context Live
⚪ Idle
nested-scope.js · 3 execution contexts
1const app = "QuestionPro"
2 
3function outer(env) {
4  const mode = env
5 
6  function inner() {
7    const msg = app + "/" + mode
8    return msg
9  }
10 
11  return inner()
12}
13 
14const result = outer("dev")
15console.log(result)
📚 Call Stack
— empty —
Variable Environment
— waiting —
3 scopes — global, outer(), inner(). Click "Next Step →" to watch all 3 execution contexts come to life.
Step 0 / 11
Topic 09
Hoisting
JavaScript moves declarations to the top of their scope before any code runs — assignments stay where you wrote them
Before any code runs, the engine scans the scope and registers all declarations — only the name, never the value.
var — hoisted as undefined
function — fully hoisted with body
What gets hoisted and how
Keyword Hoisted? Initial value Use before line
var ✓ Yes undefined undefined
function ✓ Fully Complete body Works ✓
let / const Partial TDZ — no value ReferenceError ✗
Topic 10
Temporal Dead Zone (TDZ)
The window between hoisting and initialisation for let / const
TDZ in action
var vs let/const lifecycle
Phase var let / const
Parse (hoist) registered + undefined registered only
TDZ no TDZ ReferenceError
Initialised at declaration line at declaration line
Why TDZ exists
  • Prevents using a variable before it's logically ready
  • Makes bugs visible immediately (ReferenceError) instead of silently returning undefined
  • Enables safer reasoning about constant values
Rule: always declare let/const before use. The TDZ is the engine enforcing this at runtime.
Topic 11
Closures
A function that remembers the scope it was created in — even after that scope has returned
Classic counter example
Definition: a closure is the combination of a function and its surrounding lexical environment. The outer scope record is kept alive in memory as long as any reference to the inner function exists.
Closure as a factory — practical pattern
Where closures appear
  • Event handlers referencing outer state
  • setTimeout / setInterval callbacks
  • Module pattern & private data
  • React hooks (useState, useCallback, useMemo)
  • Memoisation & partial application
Topic 12a
Async & Loop Traps
The classic var + loop bug — and three ways to fix it
😱 The trap — var in a loop
Why it breaks: var i is function-scoped — there is only one i. By the time any callback fires, the loop has finished and i === 3.
✅ Fix 1 — use let (block scope)
✅ Fix 2 — IIFE closure
✅ Fix 3 — forEach
Topic 12b
Scope in JavaScript Frameworks
How the same scope rules manifest in React and DOM event handlers
⚡ React — hooks & stale closures
handleClick closes over count. Without the dep array, it captures a stale value — classic scope bug in React.
🖱️ DOM — event handlers close over scope
Each button's handler closes over its own i because let creates a new block scope per iteration. var would share one binding — a classic loop bug.
Common theme: React hooks and DOM event handlers are all just functions — and functions create scope. Every closure, chain lookup, and TDZ rule you've learned applies directly here.
Topic 12c
Real-World Scope Patterns
Patterns you'll see in production codebases
🔒 Module pattern
_data is private — only set and get can touch it via closure.
📋 Memoisation
cache persists between calls via closure — fn only runs once per unique key.
🔄 Partial application
Output
add5(3) // → 8
add5(10) // → 15
add(2)(7) // → 9
add5 closes over a = 5 — you only need to pass b each time.
Summary
Mental Models to Take Away
🔎 Scope = visibility rules
  • Set at parse time, not runtime
  • Nesting mirrors your code structure
  • Inner sees outer; outer is blind to inner
🆋 Execution Context = runtime env
  • Created per call, destroyed on return
  • Holds: Variable Env, Scope Chain, this
  • Call stack = stack of ECs
📋 Closure = scope that survives
  • Inner function keeps outer scope alive
  • Powers: modules, hooks, factories
  • Watch the loop + async trap
Key rules at a glance
Concept One-liner
Lexical scope Where you write it is where it lives
Scope chain Walk outward until found or ReferenceError
Hoisting Declarations up, initialisations stay
TDZ let/const exist but cannot be read before init
Closure Function + its birth-scope environment
Checklist for code review
  • Using let/const instead of var?
  • No accidental globals (missing declarations)?
  • Async callbacks in loops — are closures correct?
  • Stale closure in React hooks — useCallback deps correct?
  • Module-private state not accidentally exported?