Script Loading Strategies: Synchronous, Async, Defer, and ES Modules
Master modern script loading mechanics: parser-blocking scripts, asynchronous execution (async), deferred execution (defer), and native ES Modules (type='module').
By the end, you’ll be able to…
- Analyze why default synchronous <script> tags block the HTML tokenizer and stall DOM construction
- Contrast async (executes immediately upon download) with defer (executes in DOM order at DOMContentLoaded)
- Implement native ES Modules (<script type='module'>) and explain deferred execution by default
01The Problem of Parser-Blocking Synchronous Scripts
By default, when the browser's HTML parser encounters a standard <script src="app.js"></script> tag, parsing of the HTML document halts completely. The browser must pause the HTML tokenizer, make a network request to download the script file, execute the script immediately, and only then resume HTML parsing.
Why must browsers block by default? Because synchronous JavaScript is permitted to call document.write("<p>Dynamic content</p>"), which directly injects raw markup into the parser's input byte stream! The parser cannot safely continue parsing until it knows whether JavaScript will mutate the stream.
In modern web applications, synchronous scripts located in the <head> create a massive performance bottleneck, stalling First Contentful Paint by several seconds on high-latency mobile networks.
<!-- 1. Parser-Blocking: Stalls HTML parsing until downloaded & executed --><script src="legacy.js"></script><!-- 2. Async: Downloads in background, executes IMMEDIATELY upon arrival (Interrupts parser!) --><script async src="analytics.js"></script><!-- 3. Defer: Downloads in background, executes in ORDER when DOM is parsed (Best for apps!) --><script defer src="application.js"></script><!-- 4. Module: Native ES Module (Deferred by default, strict mode, scoped) --><script type="module" src="main.js"></script>02Async vs Defer: The Crucial Execution Timing Difference
Both async and defer download external JavaScript files asynchronously in background threads without blocking HTML tokenization. However, their execution timings are radically different:
async (<script async>): Executes the instant the script finishes downloading over the network. When execution begins, HTML parsing PAUSES. If two async scripts are declared (a.js and b.js), there is NO guaranteed execution order—whichever script downloads first executes first! Use async strictly for independent third-party utilities (Google Analytics, ad trackers).
defer (<script defer>): Downloads in the background, but pauses execution until the ENTIRE HTML document has been parsed into the DOM tree. Crucially, deferred scripts preserve document order: script 1 is guaranteed to execute before script 2. Execution finishes right before the DOMContentLoaded event fires. Ideal for application business logic.
03Native ES Modules: type='module'
Modern web development standardizes on native ES Modules via <script type="module" src="main.js"></script>.
ES Modules provide four major advantages: 1. Deferred by default: Modules execute automatically with defer behavior without needing an explicit defer attribute.
2. Scoped execution: Variables declared at top-level do not pollute the global window object. 3. Strict mode: Executed in JavaScript strict mode ('use strict') automatically. 4. Native imports: Supports standard import / export syntax across modular file trees.
04Script Loading Timeline Comparison
Visualizing parser execution during script downloads:
Use defer or type="module" for your application code; use async strictly for independent analytics trackers.
HTML Parsing: [==== PARSE ====] -------- PAUSE -------- [==== PARSE ====]Sync Script: [ FETCH ] [ EXECUTE ]HTML Parsing: [=============== PARSE ===============] -- PAUSE -- [==== PARSE ====]Async Script: [ FETCHING IN BACKGROUND ] [ EXECUTE ]HTML Parsing: [======================== COMPLETE PARSE ========================]Defer Script: [ FETCHING IN BACKGROUND ] [ EXECUTE ]Event: DOMContentLoaded05Production Standards & Real-World Engineering Trade-Offs
From a browser engine rendering perspective, understanding how the HTML parser interacts with the critical rendering path is essential for front-end performance engineering. As the browser streams bytes from the network, it passes them through tokenization, tree construction, and layout phases. If external stylesheet links or blocking synchronous scripts interrupt the HTML tokenizer, the browser cannot proceed with layout or paint, resulting in white-screen delays that directly impair user conversion rates.
Furthermore, modern browser engines (Blink, Gecko, WebKit) enforce strict speculation rules. Preload scanners parse ahead in raw HTML byte streams to discover and fetch high-priority subresources—such as hero fonts, responsive images, and critical style sheets—long before the main DOM tree construction reaches those elements, cutting First Contentful Paint (FCP) times dramatically.
The ideas worth keeping.
- Synchronous scripts block HTML parsing; avoid placing raw <script> in document <head>.
- Use defer for application scripts to ensure execution occurs in order after DOM parsing.
- Use async exclusively for independent third-party trackers with no code dependencies.
Make it yours.
Edit the starter, press Preview, and see your page render in the browser preview. Compare with the worked solution when ready.
Exercise 1: Given two scripts app js and lib js where app
Given two scripts app.js and lib.js where app.js depends on functions inside lib.js, explain why <script async src="lib.js"></script> and <script async src="app.js"></script> will fail intermittently.
Explore a worked solution
// The async attribute executes each script immediately upon network download completion.// If app.js (10KB) finishes downloading before lib.js (150KB), app.js will execute first.// When app.js calls functions defined in lib.js, a ReferenceError occurs because lib.js// has not yet downloaded or executed.// Fix: Use 'defer' instead of 'async' to guarantee scripts execute in exact document order.Does async guarantee execution order between multiple files?
Exercise 2: What are two default behaviors of script typ
What are two default behaviors of <script type="module"> that differ from standard <script> tags?
Explore a worked solution
// 1. ES Modules are deferred by default (they execute after the HTML document is fully parsed).// 2. ES Modules execute in strict mode ('use strict') and have their own module scope// (top-level variables do not pollute the global window object).Think about execution timing and variable scope.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
Pause. Put the pieces together.
You’ve reached the end of HTML Foundations: Your First Page, Document Anatomy & Browser Parsing. Before moving on, use this task to check what you can do independently.
Build a valid HTML5 page with UTF-8, a responsive viewport, and a deferred script that passes W3C validation.
Try it in your own environment and write down the result. This is a self-check; reading completion does not evaluate your code.
Review this moduleOne lesson closer.
Reading is a start. Trying it yourself makes the connection.