Chapter 57: The Critical Rendering Path (CRP)

Script Execution Models — Classic vs async vs defer

Mastering Parser Blocking, Execution Timing, Dependency Graphs, DOMContentLoaded Interaction, and ES Modules.

LEARNING OBJECTIVES
  • Diagram and compare the execution timelines of Classic Synchronous, async, defer, and type="module" scripts.
  • Understand why defer preserves document execution order while async executes on a first-come, first-served basis.
  • Explain the relationship between script execution and the DOMContentLoaded lifecycle event.
  • Identify which script loading model to choose for analytics, component frameworks, and utility libraries.
  • Eliminate parser-blocking bottlenecks across legacy and modern web applications.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

Imagine a construction crew building a skyscraper with blueprints being drawn in real time:

  1. The Classic Foreman (Synchronous <script>): The builder is laying bricks. Suddenly, the blueprint calls for a specialty plumber. The builder puts down his trowel, stops all bricklaying, and stands motionless until the plumber drives across town, installs one valve, and leaves. Only then does the builder resume laying bricks.
  2. The Wildcard Courier (<script async>): The builder orders 3 separate couriers to fetch supplies (paint, tiles, fixtures). They race across town independently. Whoever arrives first barges directly onto the construction site, interrupting whatever the builder is doing to drop their supplies immediately. Order is completely unpredictable!
  3. The Organized Logistics Manager (<script defer>): The builder orders supplies at the start of the shift. All couriers drive and collect parts in the background while the builder continues laying bricks uninterrupted. Once the entire skyscraper frame is finished, the supplies are delivered and assembled in the exact numbered order they were ordered.
  4. The Modern Modular Architect (<script type="module">): High-precision prefabricated modules that naturally load in the background (like defer), isolate their internal tools from the global construction site, and assemble neatly when the building frame is ready.

Technical Deep Dive & Specifications

Visualizing the Script Execution Timelines

The following diagram illustrates how modern browser engines handle the HTML parser thread during resource fetching and JavaScript execution:

  1. CLASSIC SYNCHRONOUS (<script src="...">):
  HTML Parsing:  [ Parse HTML ] ───► [ PAUSED (Wait) ] ──────► [ Parse HTML ] ──► [ DOM Done ]
  Network:                            [ Fetch JS ]
  JS Execution:                                   [ Execute JS ]

  2. ASYNC (<script async src="...">):
  HTML Parsing:  [ Parse HTML ] ────────────────────────► [ PAUSED ] ──► [ Parse HTML ] ──► [ DOM Done ]
  Network:          [ Fetch JS (in background) ]
  JS Execution:                                           [ Exec JS ] (Executes immediately when fetched)

  3. DEFER (<script defer src="...">):
  HTML Parsing:  [ Parse HTML ] ────────────────────────────────────────► [ DOM Done ]
  Network:          [ Fetch JS (in background) ]                               │
  JS Execution:                                                                └──► [ Execute in Order ] ──► [ DOMContentLoaded ]

  4. MODULE (<script type="module" src="...">):
  HTML Parsing:  [ Parse HTML ] ────────────────────────────────────────► [ DOM Done ]
  Network:          [ Fetch JS Module Tree (bg) ]                              │
  JS Execution:                                                                └──► [ Execute in Order ] ──► [ DOMContentLoaded ]

Comparative Feature Matrix

Attribute / Type Downloads Asynchronously? Pauses HTML Parser during Download? Pauses HTML Parser during Execution? Execution Order Guaranteed? Blocks DOMContentLoaded?
<script src="..."> (Head) ❌ No ⚠️ Yes ⚠️ Yes ✅ Yes (in source order) ⚠️ Yes
<script src="..."> (End of Body) ❌ No ❌ No (DOM mostly parsed) ⚠️ Yes ✅ Yes (in source order) ⚠️ Yes
<script async src="..."> Yes ❌ No ⚠️ Yes (when download ends) No (First ready, first run) ⚠️ Only if executing
<script defer src="..."> Yes ❌ No ❌ No (runs after parse) Yes (Strict source order) ✅ Runs right before
<script type="module" src="..."> Yes ❌ No ❌ No (runs after parse) Yes (Strict dependency order) ✅ Runs right before
<script type="module" async src="..."> Yes ❌ No ⚠️ Yes (runs immediately) No (First ready, first run) ⚠️ Only if executing

Execution Semantics & Edge Cases

1. defer Guarantees

  • The defer attribute is only valid for external scripts (scripts with a src attribute). Spec-compliant browsers ignore defer on inline <script> tags.
  • All defer scripts download in parallel over HTTP/2 or HTTP/3.
  • Regardless of which network request finishes first, the browser executes defer scripts strictly in the order they appear in the HTML source code.
  • All defer scripts finish executing before the browser fires the DOMContentLoaded event on document.

2. async Non-Determinism

  • async scripts download in the background without pausing HTML parsing.
  • However, as soon as the HTTP download completes, the browser immediately suspends the HTML parser on the main thread to compile and execute the script.
  • If Script A (500KB) appears before Script B (10KB), Script B will almost certainly download first and execute before Script A. If Script B depends on functions defined in Script A, an Uncaught ReferenceError will be thrown.

3. ES Modules (type="module")

  • Modern ES modules default to defer behavior automatically: they download in the background without blocking the parser and execute in dependency order before DOMContentLoaded.
  • Adding the async attribute to an ES module (<script type="module" async>) makes it execute immediately once it and all its imported sub-modules finish downloading.

💻 Interactive Code Playground

Starter Code

Line-by-Line Code Breakdown

  • Lines 14–21 (Inline Script): Executes immediately while parsing <head>, blocking the initial tokenization.
  • Lines 24–27 (defer scripts A & B): Both scripts download in parallel. Even if Script B finishes downloading before Script A, the engine guarantees Script A runs first, followed immediately by Script B.
  • Lines 30 (async script): Fetches in parallel and executes as soon as it arrives, completely independent of the DOM parser state.
  • Lines 41–53 (DOMContentLoaded): Confirms that all defer scripts execute before DOMContentLoaded triggers, while DOM elements are fully accessible.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...
Script Execution Sequence Log:
  1204ms: 1. Inline Head Script Executed (Parser Paused)
  1206ms: HTML Parser reached End of Body
  1208ms: 2. Async Script (Independent Telemetry) Executed
  1210ms: 3. Defer Script A (Core Utility) Executed
  1211ms: 4. Defer Script B (App UI) Executed
  1212ms: 5. DOMContentLoaded Event Fired

🏋️ Hands-On Exercise

🎯 The Challenge: Resolve Race Conditions and Parser Blocking

Instructions:

  1. You are tasked with refactoring an analytics dashboard that is experiencing intermittent runtime crashes and slow load times.
  2. The current page has three major defects:
    • Synchronous jQuery in the <head> blocking FCP by 1.8s.
    • A chart plugin with async loading before jQuery, causing Uncaught ReferenceError: $ is not defined.
    • Google Analytics loaded synchronously at the top of <head>.
  3. Refactor the script tags using modern defer and async attributes so that:
    • Dependent UI scripts execute in guaranteed sequence without blocking HTML parsing.
    • Independent analytics scripts load asynchronously without blocking the UI or UI dependencies.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Using defer on Inline Scripts: Writing <script defer>console.log('test')</script> does nothing. The HTML5 specification dictates that defer is ignored on inline scripts; they run synchronously and block the parser.
  2. Using async for Dependent Code: Putting async on both a framework (e.g. React/Vue) and your application initialization code causes unpredictable intermittent production bugs when the app script arrives before the framework.
  3. Placing Scripts before Critical CSS in <head>: Placing synchronous scripts above <link rel="stylesheet"> prevents the speculative scanner from preloading styles with optimal priority.

💡 Pro Tips

  1. Default to Modern ES Modules (type="module"): By using <script type="module" src="app.js">, scripts automatically gain deferred execution semantics, strict mode by default, top-level await support, and lexical scoping that prevents pollution of window.
  2. Dynamic Script Injection Defaults to Async: When creating a script element programmatically (document.createElement('script')), browsers set script.async = true by default. If you need dynamically inserted scripts to execute in sequence, explicitly set script.async = false.

📌 Key Takeaways

  • Classic <script> pauses both HTML parsing and rendering during download and execution.
  • defer downloads in the background, never blocks HTML parsing, preserves source execution order, and executes right before DOMContentLoaded.
  • async downloads in the background, pauses the parser the instant download finishes, and executes out of order (first ready, first run).
  • Use async for independent scripts (analytics, ads, telemetry). Use defer or type="module" for application code and interdependent libraries.
  • type="module" scripts behave like defer by default while offering module encapsulation.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

If you have three scripts <script defer src="a.js">, <script defer src="b.js">, and <script defer src="c.js">, where c.js finishes downloading over the network first, in what order will they execute?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

Which script loading attribute is best suited for an independent third-party heat-mapping script (e.g. Hotjar) that does not interact with your application code?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

When does a <script defer> execute relative to document lifecycle events?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP