Reference guide · html-css-js · Published 2026-08-16 · 4 min read
JavaScript defer and async
JavaScript defer vs async: execution order explained, when to use each, and how they interact with module scripts.
- ·The two attributes
- ·Order rules
- ·Choosing the strategy
The two attributes, in one picture
Without any attribute, a <script src> is blocking: the browser stops parsing HTML, downloads the script, executes it, and only then continues parsing. That is the cause of many "page is blank while script loads" cases.
With defer, the browser downloads in parallel with parsing, then executes in document order *after* the document has finished parsing (DOMContentLoaded comes after). Deferred scripts preserve order.
With async, the browser downloads in parallel and executes as soon as it arrives, interrupting parsing at that moment, in whatever order the network returns. Asynchronous scripts do not preserve order.
| Attribute | Parsing | Execution order | Fires relative to DCL |
|---|---|---|---|
| none | blocks parsing | in order, at parse time | before DCL |
async | continues during download | whichever finishes first | possibly before DCL |
defer | continues during download | true document order | after DCL |
Both async and defer stop the renderer-bottleneck download blocking; the difference is the execution timing and the ordering guarantee.
When each is right
deferfor everything you want to run after the DOM is ready and in a predictable order. Tags like analytics, chat widgets, or page JS that relies on DOM exist and on other scripts running first. It is the default choice for self-contained engine scripts.asyncfor truly independent, small scripts that do not need the DOM or other scripts, and can run as soon as they arrive. Ad scripts, third-party badges, a CDN-provided library that nothing else waits on. If twoasyncscripts both touch shared state, order breaks.- Neither for inline-critical init and for module scripts loaded at the top that genuinely gate the page.
The render-blocking resources article explains why render-blocking matters to the LCP metric; defer and async are the lever that turns blocking scripts into non-blocking ones.
The order rules that bite
- Deferred scripts run in document order.
defer athendefer balways executes a before b, even if b finishes downloading first. - Async scripts run in the order they finish. Two
asyncscripts promise nothing about order. - Deferred scripts run before
DOMContentLoaded. They do *not* run after the page is fully interactive if DCL is already over. - When a script has both
asyncanddefer,asyncwins in browsers that honor both (they are mutually exclusive semantics).
Module scripts and the modern default
The word "defer" is largely assumed for ES modules (<script type="module">): module scripts behave like deferred scripts (downloaded in parallel, executed after parsing, in order) and add stricter correctness (static import resolution, strict mode, top-level await forbidden). For new work, shipping modules and letting the module semantics be your ordering is closer to an explicit design than mixing files.
A modern pattern that works well:
- Defer your own classic scripts that touch the DOM.
asyncthird-party that must not be gated.- Prefer modules when you control the code; the bundle size guidance tells you what to load before you add dependencies.
The debugging angle
When a script "doesn't work", the first question is which timing bucket it is in. A badly-timed init script (runs before the element it wires exists) looks identical to a genuine bug; the HTML/CSS/JS order article steps through isolating it. When a third-party snippet "fires late", usually it is async and you asked for that: it runs when the network delivers, not when the page finished. And when a page is slow on LCP specifically, an un-deferred script in the <head> is a top suspect, the waterfall article will show it as a block.
Prevention
Adopt the default "defer local scripts, async third-party" and only use the plain attribute for code that must block the head. Audit the page with the Network panel's "Script" filter; you should never see a blocking script in <head> unless it has to be there, and a module-based codebase makes the ordering question disappear entirely.