Part 1
How your code runs
What happens between saving a file and seeing it run.
01 The engine: from text to machine code
A browser or Node doesn't run your file as-is. A JavaScript engine reads it, turns it into a tree, runs it quickly in an interpreter, and then compiles the parts that run a lot into fast machine code — making bets about your types as it goes.
To the engine, a script starts as a string of characters. Nothing has run yet. Engines like V8 (Chrome, Node), SpiderMonkey (Firefox) and JavaScriptCore (Safari) all take it through the same broad stages.
02 The call stack
JavaScript runs one thing at a time. It tracks where it is with a stack: calling a function pushes a frame on top, returning pops it off. Whatever is on top is what's running right now. Too many frames and you get the famous Maximum call stack size exceeded.
Call factorial(3). It needs 3 × factorial(2), so it pauses and pushes a new frame.
03 Hoisting and the temporal dead zone
Before any line in a scope runs, the engine creates every variable and function that scope declares. var starts as undefined, functions start ready to call, and let / const exist but can't be touched until their line runs.
creation phase
scope bindings
scanning…
Before a single line runs, the engine scans the scope and creates every binding it declares. "Hoisting" is not code moving upwards — it is bindings existing before execution starts.
04 Scope and closures
Every function can see the variables where it was written, not where it's called. When a function outlives the function that created it, it keeps those variables alive — that's a closure, and it's how JavaScript does private state.
A variable lookup walks outward through the scope chain. The closure is increment() keeping makeCounter's scope alive after it returned.
05 The this keyword
Unlike scope, this is decided by how a function is called, every single time. Four rules cover almost everything, and arrow functions opt out of all of them.
this is not decided where a function is written. It is decided each time the function is called, by how the call looks. Four rules cover almost every case.
Part 2
Memory
Where your data lives, and how the engine cleans up after you.
06 The stack and the heap
A running program uses two kinds of memory. The stack holds each function's local variables and disappears the moment the function returns. The heap holds objects, which live on for as long as something still points to them.
Memory has two main areas. The stack holds one frame per running function, with its local variables. The heap is a big pool where objects live for as long as something points to them.
07 Values, references, and copying
Numbers and strings are stored directly. Objects and arrays aren't — a variable holds a reference to them. Spread ({ ...obj }) copies only the top level, so nested objects stay shared between the copy and the original.
Two objects on the heap: the user (#1) and the address it points to (#2). The address field does not contain the address — it holds a reference to it.
08 Garbage collection
You never free memory yourself in JavaScript. Every so often the garbage collector starts from the roots — globals and the variables of running functions — marks everything it can reach, and frees the rest. Objects that only point at each other still get collected.
The heap is a graph: objects pointing at objects. The roots are where every reference chain starts — global variables and the local variables of functions currently on the stack.
Part 3
Values and objects
How values convert, how objects share behaviour, and how the engine keeps them fast.
09 Type coercion
When an operator gets values of the wrong type, JavaScript quietly converts them. The rules are consistent, but they combine into famous surprises like '5' + 1 being '51'. Knowing the few rules — and using === — removes the mystery.
When either side of + is a string, + means "join text". The number 1 is converted to the string "1", and the result is "51", not 6.
10 Prototypes
JavaScript objects don't copy methods from a class. Each object has a hidden link to another object, its prototype. When a property isn't found, the engine follows that link, and the next, until it finds it or reaches null. class is a nicer way to set up the same chain.
We call dog.eat(). The engine needs to find an "eat" property. Press play to watch it walk the chain.
11 Hidden classes and inline caches
Property lookups are fast because the engine quietly gives every object a hidden shape: a layout that says which property sits in which slot. Objects built the same way share a shape, and each property access remembers the shapes it has seen, so it can skip the lookup next time.
Objects look like dictionaries, but looking a name up in a dictionary on every property access would be slow. So the engine gives each object a hidden "shape" describing its layout: which properties it has, and at which slot.
12 Immutability and structural sharing
Instead of changing an object, make a new one — but only copy the path that changed and reuse everything else. It's cheap, and it lets React and friends tell what changed with a simple ===.
green = newly allocated · dashed = reused by reference
An immutable state tree. We want to change one leaf — the theme — without mutating anything.
Part 4
Asynchronous JavaScript
How a single-threaded language waits for timers, networks, and users.
13 The event loop
Timers, network requests and clicks are handled outside JavaScript. When they finish, their callbacks wait in a queue. The event loop moves the next one onto the call stack — but only when the stack is empty, and promise callbacks (microtasks) always go before timers (tasks).
Press Play (or step with Next) to watch the script run.
14 Promises
A promise is a placeholder for a value that isn't ready yet. It starts pending and settles exactly once — fulfilled with a value or rejected with an error. .then and .catch register what should happen next.
A promise settles exactly once — fulfilled or rejected — and never changes state again. Extra resolve/reject calls are silently ignored.
15 async / await
await doesn't block anything. It pauses just this function, hands control back to the event loop, and resumes it as a microtask when the promise settles. The code reads top to bottom but runs in pieces.
Press Play (or step with Next) to watch the script run.
16 Generators
A generator is a function that can pause in the middle with yield and pick up later exactly where it stopped, local variables intact. It's the machinery async functions are built on.
const g = counter() — calling a generator runs NO code. It hands back a paused iterator. Generators are lazy.
17 Cancellation with AbortController
Promises can't be cancelled on their own. An AbortController gives you a signal you pass into fetch, listeners and your own code, so one call to abort() stops all of it.
cleanup performed
—
One AbortController produces one signal, and any number of operations can listen to it. Here three async operations share a single signal.
Part 5
The browser
Turning code into pixels, and keeping the page responsive.
18 From DOM to pixels
After JavaScript changes the page, the browser recalculates styles, works out where every box goes (layout), paints them, and composites the layers. Changing size or position redoes layout; changing transform or opacity can skip straight to compositing.
Every visual change runs through the same pipeline. Which stages it reaches decides whether a frame costs microseconds or milliseconds.
19 Debounce and throttle
Scrolling, typing and resizing fire dozens of events a second. Debounce waits until they stop; throttle lets one through at a fixed rate. Both keep expensive work from running on every event.
Mash the button in bursts, pause, then burst again. Debounce waits for silence; throttle fires immediately but at most once per 800 ms. The timeline covers 10 seconds, then starts over.
20 Web Workers
The main thread runs your JavaScript and draws the page, so a long calculation freezes everything. A Web Worker runs code on a separate thread and talks to the page by passing messages.
everything on the main thread
main thread
heavy work in a worker
main thread
worker thread
green = frame painted · red = frame dropped · amber = worker computing · dark = postMessage
Two timelines, 16ms per slot. Green means the browser painted a frame on time; the page feels alive.
21 How React updates the page
React re-runs your components, compares the new result with the last one, and applies only the differences to the DOM. In lists it matches items by key — so the key decides which item keeps which state.
Two copies of the same list of rows, each with an uncontrolled input. The only difference is the key: array index on the left, a stable id on the right.
Part 6
Beyond the browser
The same engine on a server, moving more data than fits in memory.
22 Streams and backpressure
Node reads big files and network data in chunks instead of all at once. When the reader is faster than the writer, backpressure tells it to pause, so memory stays flat no matter how large the data is.
no backpressure
producer running
buffered 0 · produced 0 · consumed 0
with backpressure
producer running
buffered 0 · produced 0 · consumed 0
A producer reads rows far faster than the consumer can write them. Top: no backpressure. Bottom: the producer waits for the consumer.