async-defer
Control when external scripts download and execute relative to HTML parsing. Three strategies for balancing page load speed with script execution order.
Overview
By default, when the browser encounters a <script> tag, it stops parsing HTML, downloads the script, executes it, and only then resumes parsing. For large scripts, this creates a visible delay before the page renders. The async and defer attributes change this behavior by allowing the download to happen in parallel with parsing.
Applies to: <script> with a src attribute (external scripts only). These attributes have no effect on inline scripts.
Values
| Strategy | Downloads | Executes | Execution Order |
|---|---|---|---|
| (none) | Blocks parsing during download | Immediately after download | In document order |
async | In parallel with parsing | As soon as download finishes (pauses parser) | Not guaranteed |
defer | In parallel with parsing | After HTML is fully parsed, before DOMContentLoaded | In document order |
type="module" | In parallel with parsing (deferred by default) | After HTML is fully parsed | In document order |
Normal Loading (No Attribute)
The parser stops at each script tag, fetches the file, executes it, then continues. This guarantees execution order but delays rendering.
async
The script downloads in the background while parsing continues. As soon as the script finishes downloading, the parser pauses to execute it. If multiple async scripts are present, they execute in whichever order they finish downloading, not document order.
Best for: Independent scripts that do not depend on other scripts or DOM state — analytics, ad tags, monitoring, A/B testing.
defer
The script downloads in the background like async, but execution is postponed until HTML parsing is complete. Multiple deferred scripts execute in the order they appear in the document, and all run before the DOMContentLoaded event fires.
Best for: Application scripts that need the full DOM and depend on each other's execution order — frameworks, UI code, form handlers.
type="module"
ES module scripts are deferred by default without needing the defer attribute. They also support top-level await, strict mode is always on, and each module has its own scope (no global variable pollution).
Adding async to a module script makes it execute as soon as it and its dependencies are ready, similar to async on classic scripts.
Decision Table
| Scenario | Use | Reason |
|---|---|---|
| Analytics, ads, third-party widgets | async | Independent; order does not matter; should not delay page |
| App scripts, UI initialization | defer | Needs the DOM; depends on load order |
| Framework/library followed by app code | defer (both) | Guarantees library loads before app code |
| Modern ES modules | type="module" | Deferred by default; supports import/export |
| Script that must run before any rendering | (none) | Rare; only for critical inline polyfills or theme detection |
Practical Example
Limitations
- Inline scripts ignore async/defer: These attributes only work on scripts with a
srcattribute. Inline scripts always execute immediately (excepttype="module"inline scripts, which are deferred). - async order is unpredictable: If script B depends on script A, do not use async on both. Use defer to preserve order.
- defer and DOMContentLoaded: Deferred scripts delay the
DOMContentLoadedevent. A slow deferred script holds up any code waiting for that event. - Both attributes together: If both
asyncanddeferare present,asynctakes precedence in browsers that support it.deferacts as a fallback for very old browsers. - Dynamic script injection: Scripts added via
document.createElement('script')are async by default. Setscript.async = falseto preserve insertion order.