Skip to the chapter
←JavaScriptGGroundworkJSJavaScript
JavaScript/Beginner/B6
6 / 41 written chapters
/

The cover
  1. B1Setup & mental model
  2. B2Execution context
  3. B3var, let & const
  4. B4Scope
  5. B5Single thread
  6. B6In the browser
  7. B7Types & values
  8. B8Strings & Math
  9. B9Operators & flow
  10. B10Functions
  11. B11Objects & arrays
  12. B12this
  13. B13Closures
  14. B14DOM & events
  15. B15Callbacks & promises
  16. B16Errors & tools
  17. B17Guided project
  18. ★Cheat page
  1. I1Scope & functions, properly
  2. I2Objects deeply
  3. I3Prototypes & OOP
  4. I4Async, properly
  5. I5Modules & tooling
  6. I6Regex & dates
  7. I7Error handling
  8. I8Real-time connections
  9. I9Offline & storage
  10. I10Browser APIs
  11. I11Modern JS
  12. ★Cheat page
  1. A1Engine & memory
  2. A2Advanced async
  3. A3Metaprogramming
  4. A4Types & data
  5. A5Patterns & architecture
  6. A6Performance
  7. A7Security
  8. A8Ecosystem
  9. A9Testing
  10. A10Observability
  11. A11Interview bank
  12. A12Build it yourself
  13. ★Cheat page

JSB6 · Part 1 · Beginner

Inside the browser

From a script tag to pixels on screen — and where your code sits in that.

8 min read1 diagram8 sections2 exercises
6 of 41

In this chapter

  • The journey your file actually takes
  • Step 1 — the parser meets your script tag
  • Step 2 — what the engine does with the file
  • Step 3 — pixels, and the 16 millisecond budget
  • Where the event loop fits in a frame

and 3 more

about 8 min
08On this page
  1. The journey your file actually takes
  2. Step 1 — the parser meets your script tag
  3. Step 2 — what the engine does with the file
  4. Step 3 — pixels, and the 16 millisecond budget
  5. Where the event loop fits in a frame
  6. The events that tell you the page is ready
  7. Seeing it for yourself
  8. What to carry forward

The journey your file actually takes

You write app.js. Somewhere between that and a button lighting up on screen, a lot happens that nobody shows you. Here is the whole route, and then each stop in turn.

HTML bytes CSS bytes your JS engine: parse → bytecode DOM + CSSOM two trees, then merged Render tree only what is actually visible layout → paint → composite → PIXELS where it is · what colour · stitched together on the GPU JS can rewrite the DOM — and often does Your JavaScript and the rendering work share one thread. They take turns; they never overlap.
Your code and the pixels it changes are queued on the same thread.

Step 1 — the parser meets your script tag

The browser reads HTML top to bottom, building the DOM as it goes. Then it hits a <script>, and by default it does something drastic: it stops. Parsing halts, the file is downloaded, the file is executed, and only then does HTML parsing resume.

This is not an accident. A classic script is allowed to call document.write and inject markup at that exact position, so the browser cannot safely read ahead. One slow script in <head> means a blank page until it lands — no text, no layout, nothing.

Page load — blocking vs defer vs async
HTML parser
Downloading in the background
Running on the main thread
What the user sees

plain defer async
Blocks HTML parsing yes no only while it executes
Runs when immediately, in place after the DOM is parsed the moment it arrives
Keeps source order yes yes no — whoever lands first
Can it see the whole DOM? only what is above it yes, all of it no guarantee
Use it for almost nothing your app code independent scripts: analytics

<script type="module"> is deferred automatically — you get defer behaviour without asking. That is one more reason the module version is the sane default.

⚠ The classic "it's null!" bug A plain script in <head> that calls document.querySelector("#app") gets null — not because the selector is wrong, but because the parser never reached that element. Move the tag to the end of <body>, add defer, or wait for DOMContentLoaded.

Step 2 — what the engine does with the file

Your source is text. The CPU runs machine code. The engine (V8 in Chrome and Node, SpiderMonkey in Firefox, JavaScriptCore in Safari) closes that gap in stages:

  1. Tokenise — chop the text into meaningful pieces: const, total, =, 42.
  2. Parse into an AST — an abstract syntax tree, the shape of your program as data. A syntax error is thrown here, before a single line runs, which is why one stray brace kills the entire file rather than just its own line.
  3. Compile to bytecode — compact instructions an interpreter executes right away. This is why JS starts fast instead of waiting on a full compile.
  4. Watch, then optimise — the engine counts how often each function runs. A function that runs constantly is "hot", so an optimising compiler rewrites it into real machine code, betting on the types it has seen so far.
  5. Deoptimise when the bet fails — feed a function that always got numbers a string, and the optimised version is thrown away and the engine falls back to bytecode.

Two useful consequences at beginner level. First, syntax errors are found before execution — so a typo at the bottom of the file stops the top of it from running. Second, keeping a function's inputs the same shape is not folklore, it is what keeps the optimised version alive. The full story, plus how to see it happening, is in Engine & memory.

console.log("does this line run?");

// A syntax error further down — uncomment to see
// const broken = ;

console.log("parse succeeded, so both lines print");

Remove the comment markers and run it again: the first log never appears. Nothing executed, because parsing never finished.

Step 3 — pixels, and the 16 millisecond budget

Most screens redraw 60 times a second. That gives the browser about 16.7ms to produce each frame — and your JavaScript shares that window, because it is the same thread. A frame is built in four stages:

  • Style — which CSS rules apply to which element now.
  • Layout (reflow) — the geometry: how big everything is and where it sits. Changing width, top or text content forces this, and it cascades to children.
  • Paint — filling in pixels: colours, shadows, text.
  • Composite — stitching the painted layers together, usually on the GPU. transform and opacity can often be done here alone, skipping layout and paint entirely — which is exactly why animating those two is smooth and animating left is not.

Rendering only gets its turn when the stack is empty. So a function that takes 100ms does not "slow down" the animation — it deletes six frames outright.

One long task, six dead frames

Dry run: counting the frames a 100ms handler actually costs

Same numbers as the demo above, as a table. Each frame gets a 16.7ms budget; the click handler needs 100ms in one go, starting partway through frame 3.

FrameMain threadRendered on screen?
1idle between frames — style, layout, paint, composite run in budgetyes
2same as frame 1yes
3a click starts the 100ms handler — the thread is now busyno — due, but the stack is not empty
4–8 (5 frames)still inside the same 100ms handlerno — each one arrives, finds the thread busy, and is simply never built
9handler returns partway through — thread frees upyes — but jumps straight to the new state instead of animating through it

16.7ms × 6 ≈ 100ms — one blocking call is long enough to swallow six consecutive frame budgets whole (frame 3 through frame 8). Nothing queues those frames for later: a frame the browser could not build in its window is simply skipped, not delayed.

Where the event loop fits in a frame

Put the last chapter and this one together and the browser's turn order becomes concrete. Per pass, roughly:

  1. run one task from the queue — a timer, a click handler, a network callback
  2. drain every microtask queued by it, completely
  3. run requestAnimationFrame callbacks — the moment designed for "change something visual"
  4. style, layout, paint, composite — if there is time before the next frame

Two practical rules fall out of that list. requestAnimationFrame is where visual updates belong, because it runs immediately before rendering rather than at some arbitrary point mid-frame. And reading a geometric property like offsetHeight right after writing a style forces the browser to run layout early, in the middle of your loop, to answer you honestly — do that inside a loop over 200 elements and you have written the classic layout thrash.

// Thrashing: read, write, read, write - a forced layout each pass
for (const box of boxes) {
  box.style.height = box.offsetHeight + 10 + "px";
}

// Batched: all reads, then all writes - one layout
const heights = boxes.map((b) => b.offsetHeight);
boxes.forEach((b, i) => { b.style.height = heights[i] + 10 + "px"; });

The events that tell you the page is ready

Event Fires when Use it for
DOMContentLoaded HTML parsed, deferred scripts done. Images may still be coming. almost everything — finding elements, wiring listeners
load every image, stylesheet and iframe has finished measuring images, final-size layout work
beforeunload the user is leaving "you have unsaved changes" — sparingly

If your script is deferred or sits at the end of <body>, you usually need neither. The DOM is already there.

Seeing it for yourself

None of this is theoretical — the browser will show you every stage. In DevTools, the Network panel shows which script blocked the parser, and the Performance panel records a real load: a flame chart of the call stack over time, long tasks flagged in red, and the layout/paint work in between. Record five seconds of your own page and the pipeline in this chapter appears, labelled, in front of you.

Say it like this → "The browser parses HTML into a DOM, and stops dead at every plain script tag. The engine turns my code into bytecode and optimises what runs hot. Then rendering and my JavaScript take turns on one thread, sixty times a second — so a long function does not slow the page down, it skips frames."

What to carry forward

  • defer (or a module) for your own scripts; async only for things that do not touch your DOM.
  • Syntax errors are found at parse time, before anything runs.
  • 16.7ms per frame, shared with your code. Over ~50ms in one function and users feel it.
  • Animate transform and opacity; they can skip layout and paint.
  • Batch DOM reads and writes instead of alternating them.
Interview answer, one sentence

"The parser stops for a synchronous script, so defer, async or a module keeps it from blocking; after that the event loop has roughly 16 ms a frame for JavaScript, style, layout and paint, and long tasks on the main thread are what make a page feel slow."

Before you move on
  • Explain why a plain <script> in <head> can make document.querySelector return null, and name two fixes.
  • Compare defer and async on whether they block parsing, when they run, and whether source order is kept.
  • Explain why a syntax error at the bottom of a file stops a console.log at the top from ever running.
  • Given a function that blocks the thread for 100ms, calculate roughly how many 16.7ms frames it costs — and say why "delayed" is the wrong word for what happens to them.
  • Say which two CSS properties can animate without triggering layout or paint, and why batching DOM reads before writes avoids layout thrash.

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsPredict what runs whenbeginner
  • 8 testsCount the frames you droppedbeginner
All 35 for this level →
B6

Finished reading?

0 of 41 chapters read in this series

→Next stop

  1. B5Previous · B5One thread, one stack
  2. B6you are here
  3. Next · B7Types & valuesEight types, one conversion table, and the equality check that never stops mattering.9 min read

01On this page

8 sections · 0%

  1. 01The journey your file actually takes
  2. 02Step 1 — the parser meets your script tag
  3. 03Step 2 — what the engine does with the file
  4. 04Step 3 — pixels, and the 16 millisecond budget
  5. 05Where the event loop fits in a frame
  6. 06The events that tell you the page is ready
  7. 07Seeing it for yourself
  8. 08What to carry forward

[] chapters · t top