Lesson 01

How a browser actually works

The exact thing they will drill you on. Don't skim β€” every section ends with a sentence to drop in the interview.

1 Β· How the browser draws a page

🧠 in plain english
Think of the browser like a pizza restaurant. You order (write code). The kitchen has 5 stations: read the order, figure out toppings, roll the dough, cook it, plate it. If you change a topping, only stations 2-5 redo their work. If you change the size of the dough, ALL stations redo. If you just change the table number on the receipt, only the last station redoes. Browser pipeline = same thing.
1
JavaScript runs first β€” your code (e.g. el.style.color = "red").
2
Style β€” browser figures out which CSS rules apply.
3
Layout β€” calculates where every element goes (x, y, width, height). EXPENSIVE. Triggered by changing size, position, font.
4
Paint β€” fills in pixels (color, borders, shadows). Medium cost.
5
Composite β€” GPU stacks the painted layers. CHEAP. Triggered ONLY by transform, opacity, filter.

Click any stage below to see what triggers it:

cost: β€”
Layout: Calculates positions and sizes. EXPENSIVE. Triggered by changing geometry, reading offsetHeight after a write, font load, viewport resize.
The golden rule: animate with transform and opacity. Anything else dirties layout or paint and costs you frames.
πŸ“£ say this in the interview
β€œI always animate with transform and opacity β€” they skip layout and paint and run on the GPU compositor. Width or top/left forces a full reflow on every frame.”
⚑ quick check
Which of these is the cheapest type of change for the browser to apply?

2 Β· Layout thrashing β€” the classic perf bug

🧠 in plain english
Imagine a chef weighing flour, then adding water, then weighing again, then adding more water, alternating. The scale has to recalibrate every time. Slow.Now imagine: weigh flour 10 times, write down all the weights, THEN add water 10 times. The scale only recalibrates once. Fast.Same idea β€” interleaving reads and writes to layout properties forces the browser to reflow on every read.

This is THE classic perf interview question. Step through the code line-by-line below β€” watch reads (blue) and writes (yellow) hit the boxes, watch the reflow counter climb. Press play. Then compare with the GOOD version.

Layout thrashing β€” the BAD pattern
js
step 0 / 18
1// ❌ BAD β€” interleaves reads and writes
2for (let i = 0; i < boxes.length; i++) {
3 const h = boxes[i].offsetHeight; // READ
4 // browser must reflow before this read can return
5 boxes[i].style.height = (h + 1) + 'px'; // WRITE
6 // write invalidates layout
7}
DOM boxes
bad
12px
12px
12px
12px
12px
12px
12px
12px
layout reflows
0
1 per iteration β€” SLOW
phase
β€”
read write
press β–Ά play to step through this code, or use ← β†’ to scrub.

Now the fix β€” same problem, batched correctly. Watch the reflow count stay at 1:

Batched read+write β€” the GOOD pattern
js
step 0 / 10
1// βœ… GOOD β€” batch reads, then batch writes
2const heights = boxes.map(b => b.offsetHeight); // ALL READS
3// only one reflow needed for all reads
4boxes.forEach((b, i) => {
5 b.style.height = (heights[i] + 1) + 'px'; // ALL WRITES
6});
7// browser coalesces writes β€” one final reflow
DOM boxes
bad
12px
12px
12px
12px
12px
12px
12px
12px
layout reflows
0
1 per iteration β€” SLOW
phase
β€”
read write
press β–Ά play to step through this code, or use ← β†’ to scrub.

Want to feel it on real DOM? 300 live boxes, real timing, real fps:

last: β€” ms Β· fps: 0
BAD timeline
click a button β†’
GOOD timeline
click a button β†’

πŸ“£ say this in the interview
β€œLayout thrashing happens when reads and writes to layout-affecting properties interleave. Each write invalidates layout; the next read forces a synchronous reflow. Batching reads before writes lets the browser coalesce work.”

3 Β· Memory leaks in React

🧠 in plain english
You leave a tap running every time you wash dishes. Each β€œwash” (component mount) leaves the tap on (event listener, interval, subscription). Eventually the kitchen floods (memory grows forever). The cleanup function in useEffect is the off-switch for the tap.

The 5 most common React memory leaks:

  • useEffect with no cleanup β€” listener / interval / subscription stays alive after unmount.
  • Closures capturing big objects β€” held alive by a long-lived reference somewhere.
  • Detached DOM nodes β€” element removed from the tree but still kept in a ref or array.
  • Global stores never cleared β€” Redux/Zustand entries that grow forever.
  • WebSocket / Socket.io / SSE with no close() on unmount.
// ❌ leaks β€” the listener references the unmounted component
useEffect(() => {
  window.addEventListener("resize", handleResize);
  // no cleanup! handler is permanent.
}, []);

// βœ… cleanup function returns the off-switch
useEffect(() => {
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);
Detect with Chrome DevTools β†’ Memory tab β†’ take heap snapshot β†’ search β€œDetached”. Growing closure size between snapshots = leak.
⚑ quick check
A user opens and closes a modal 100 times. The modal mounts a useEffect that subscribes to window scroll, with no cleanup. What happens?

4 Β· The event loop

🧠 in plain english
Imagine a bartender (the browser) with two customers: Promise customers (microtasks) and Timeout customers (macrotasks). The rule: every time the bartender finishes one Timeout customer, they MUST clear ALL waiting Promise customers before serving the next Timeout customer. That's why Promise.then(fn) always runs before setTimeout(fn, 0).

Step through this real code line-by-line. Watch the call stack, microtask queue, and macrotask queue update as each line executes. The output order reveals why Promise.then always runs before setTimeout(0).

Event loop β€” line-by-line
js
step 0 / 15
1console.log("1"); // sync
2
3setTimeout(() => {
4 console.log("5"); // macrotask
5}, 0);
6
7Promise.resolve()
8 .then(() => console.log("2")) // microtask
9 .then(() => console.log("3")); // microtask
10
11console.log("4"); // sync
Call stack
Console output
Microtasks (Promise)
Macrotasks (setTimeout)
press β–Ά play to step through this code, or use ← β†’ to scrub.

Want a more abstract animation of the same concept?

Call stack
Microtasks
Macrotasks
Console output
After each macrotask, the entire microtask queue drains before the next macrotask runs.
Microtasks (Promise.then, queueMicrotask, MutationObserver) drain fully between every macrotask (setTimeout, setInterval, I/O, UI events). A long Promise chain can starve the UI if it never yields.
πŸ“£ say this in the interview
β€œThe browser drains all microtasks between macrotasks. So Promise.then always runs before setTimeout zero. If your then-chain is too long, the click handler β€” which is a macrotask β€” feels frozen even if your code is fully async.”

5 Β· Quick cheatsheet β€” things to memorize

AI tutor
llama-3.1-8b-instant
Ask anything about the interview
Concept clarifications, β€œexplain X simpler,” rehearsal phrasing, follow-up questions to model answers.