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

StrategyDownloadsExecutesExecution Order
(none)Blocks parsing during downloadImmediately after downloadIn document order
asyncIn parallel with parsingAs soon as download finishes (pauses parser)Not guaranteed
deferIn parallel with parsingAfter HTML is fully parsed, before DOMContentLoadedIn document order
type="module"In parallel with parsing (deferred by default)After HTML is fully parsedIn 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.

<!-- Normal (no attribute): blocks HTML parsing --> <!-- Parser stops, downloads script, executes it, then resumes parsing --> <script src="/js/blocking.js"></script>

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.

<!-- async: downloads in parallel, executes as soon as ready --> <!-- Execution order is NOT guaranteed between multiple async scripts --> <script src="/js/analytics.js" async></script> <script src="/js/ads.js" async></script>

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.

<!-- defer: downloads in parallel, executes after parsing in document order --> <!-- Guaranteed to execute in the order they appear --> <script src="/js/framework.js" defer></script> <script src="/js/app.js" defer></script>

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.

<!-- type="module" scripts are deferred by default --> <script type="module" src="/js/app.js"></script> <!-- You can add async to a module to make it execute as soon as ready --> <script type="module" src="/js/analytics.js" async></script> <!-- Modules support top-level await --> <script type="module"> const data = await fetch('/api/config').then(r => r.json()); console.log(data); </script>

Decision Table

ScenarioUseReason
Analytics, ads, third-party widgetsasyncIndependent; order does not matter; should not delay page
App scripts, UI initializationdeferNeeds the DOM; depends on load order
Framework/library followed by app codedefer (both)Guarantees library loads before app code
Modern ES modulestype="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

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Loading Strategy Example</title> <!-- Critical CSS: render-blocking (intentional) --> <link rel="stylesheet" href="/css/main.css" /> <!-- Framework: defer ensures it runs before app.js, after parsing --> <script src="/js/framework.js" defer></script> <script src="/js/app.js" defer></script> <!-- Analytics: independent, order does not matter --> <script src="/js/analytics.js" async></script> </head> <body> <h1>Content is visible before scripts execute</h1> </body> </html>

Limitations

  • Inline scripts ignore async/defer: These attributes only work on scripts with a src attribute. Inline scripts always execute immediately (except type="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 DOMContentLoaded event. A slow deferred script holds up any code waiting for that event.
  • Both attributes together: If both async and defer are present, async takes precedence in browsers that support it. defer acts as a fallback for very old browsers.
  • Dynamic script injection: Scripts added via document.createElement('script') are async by default. Set script.async = false to preserve insertion order.

See Also

  • blocking — explicit render-blocking control for scripts and styles
  • nonce — CSP allowlisting for inline scripts
  • loading — lazy loading for images and iframes
  • integrity — verify external script contents