Skip to content

async / defer (script attributes)

Two `<script>` attributes that change when and how the script blocks the parser. `async` runs whenever it's ready; `defer` waits until parsing completes; neither blocks HTML parsing.

Three loading modes for an external <script src="...">:

No attribute (default) -- the worst case for performance. The browser stops parsing HTML, fetches the script, executes it, and only then resumes parsing. Every render-blocking script in <head> adds its full network round-trip + execution time directly to LCP.

async -- the script downloads in parallel with parsing. As soon as it's ready, parsing pauses to execute the script, then resumes. Execution order is NOT guaranteed (whichever finishes first runs first). Use for analytics / independent scripts that don't interact with the DOM or other scripts.

defer -- the script downloads in parallel with parsing AND waits to execute until parsing is complete. Defer scripts run in source order, so dependencies between them work correctly. Use for scripts that interact with the DOM (the DOM is guaranteed to exist when they run) or that depend on each other.

type="module" -- modules are deferred by default (no need to write defer explicitly). Module scripts also enable native ES module imports, top-level await, and strict mode.

Decision flow:

  • Independent / analytics: async
  • Touches the DOM, has dependencies: defer
  • Modern code, ES modules: type="module" (no need for defer)
  • "I just want this to work" default: defer

Common bug: Putting <script src="bundle.js"></script> (no attributes) in <head>. Every visitor pays the bundle's full fetch + parse time as LCP delay. Adding defer is a one-character fix with no behavior change for properly-written scripts.

Related terms

Further reading

Send Feedback