JavaScript script loading strategies (defer, async, modulepreload)
`<script>` (default): parser-blocking. `async`: fetched in parallel, executed ASAP, may interleave with parsing. `defer`: fetched in parallel, executed after parse in order. `type="module"`: defer by default. `modulepreload`: warm the cache for module dependencies. Rule of thumb: `defer` app code, `async` independent (analytics), preload critical, modulepreload module graphs.
The four behaviors
| Attribute | Fetch | Execute | Order |
|---|---|---|---|
| (none) | Sync, blocks parser | Immediately, blocks parser | Source order |
async | Async, in parallel | ASAP after download | Whichever finishes first |
defer | Async, in parallel | After parse | Source order |
type="module" | Async, in parallel | After parse (defer by default) | Source order |
Diagrams (mental)
[Sync script]
HTML parse: ▮▮▮▮[stop]▮▮▮▮▮▮▮ → script DL+exec → continue
[async]
HTML parse: ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮
script fetch: ──────────exec→ (may interrupt parse during exec)
[defer]
HTML parse: ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ → DOMContentLoaded → exec in order
script fetch: ─────────Rules of thumb
- App code that touches the DOM →
defer(or module). - Independent scripts (analytics, A/B test loaders, error reporters) →
async. - Critical above-the-fold scripts →
<link rel="preload">+ use early. - Module graphs with many imports →
<link rel="modulepreload" href="...">for each.
Module preload
ES module imports cascade — a module's imports aren't discovered until it's fetched and parsed:
<script type="module" src="/app.js"></script>app.js imports ./router.js, which imports ./pages.js — the browser learns about each only after the previous one loads. Waterfalls.
Mitigate with modulepreload:
<link rel="modulepreload" href="/router.js">
<link rel="modulepreload" href="/pages.js">
<script type="module" src="/app.js"></script>Browser fetches all in parallel.
preload vs prefetch vs preconnect
| Hint | Use |
|---|---|
preload | Resource needed for THIS navigation — fetch ASAP, high priority. |
prefetch | Likely needed for NEXT navigation — fetch idle priority. |
preconnect | Open a connection (DNS + TCP + TLS) to an origin you'll hit soon. |
dns-prefetch | Just resolve DNS for an origin. |
modulepreload | Like preload but for ES module dependencies (parses + populates module map). |
Pitfalls
asyncon app code that depends on order: scripts may execute in any order.- Forgetting
type="module"when using import statements → SyntaxError. - Module + sync anti-pattern (
<script src="module.js">without type=module). - Inline scripts ignore async/defer.
Order of execution on a typical page
- Parser-blocking sync scripts (avoid).
- CSS parses in parallel (render-blocking but not parser-blocking for non-CSS-querying scripts).
- async scripts execute as they arrive.
- defer + module scripts execute after parse in source order.
- DOMContentLoaded.
- Load event after subresources.
Interview framing
"Default <script> is parser-blocking — never put it in head without defer/async. defer: fetched in parallel, executed after parse in source order — right for app code. async: fetched in parallel, executed ASAP — right for independent scripts like analytics. type="module" is defer by default. ES modules cascade imports — use <link rel="modulepreload"> for each module in the critical graph to fetch them in parallel instead of a waterfall. <link rel="preload"> for non-module critical resources; prefetch for next-nav; preconnect for cross-origin handshakes."
Follow-up questions
- •When does async cause bugs?
- •How does modulepreload differ from preload?
- •Why is type=module defer by default?
Common mistakes
- •Sync script in head.
- •async on order-dependent app code.
- •Forgetting modulepreload → import waterfalls.
Performance considerations
- •defer/async cut TTI dramatically. modulepreload avoids module waterfall (multi-second wins on deep graphs).
Edge cases
- •Inline scripts ignore async/defer.
- •Dynamic <script> insertion semantics.
- •Importmaps + module resolution.
Real-world examples
- •Next.js Script component strategies, web.dev preload guides, Lighthouse render-blocking audits.