Skip to main content

The defer attribute in script

defer is a <script> attribute that tells the browser: download this script in parallel with the HTML, but execute it only after the whole HTML document has been loaded and parsed. Execution happens before the DOMContentLoaded event, so by that point the DOM is fully built.

Theory

TL;DR

  • With no attributes a <script> halts HTML parsing while the file is downloaded and executed.
  • defer downloads the script in parallel with HTML parsing and never blocks it.
  • Execution is postponed until the DOM is built and happens right before DOMContentLoaded.
  • Several defer scripts run in the order they appear in the markup; with async the order is arbitrary.
  • defer in <head> is the standard way to include the main application scripts.
  • defer only applies to external scripts with src; for inline code it is ignored.

Quick example

html
<head> <!-- downloads in parallel with the HTML, runs on a ready DOM --> <script src="main.js" defer></script> </head> <body> <p>Hello!</p> </body>

Normal behaviour without defer

html
<script src="app.js"></script>

What happens:

  1. The browser starts loading the HTML.
  2. It meets a <script>.
  3. It stops parsing the HTML in order to:
    • download app.js,
    • execute it.
  4. Only then does it continue building the page.

As a result the page loads more slowly, because JS blocks the HTML. That is exactly why scripts used to be placed at the end of <body>.

What defer does

html
<script src="app.js" defer></script>

Now the browser:

  1. Starts loading HTML and JS in parallel.
  2. Does not block HTML parsing.
  3. Executes app.js only after the DOM is fully built, but before the DOMContentLoaded event.

So the sequence is:

  • the HTML is parsed,
  • the script downloads in the background,
  • the DOM is ready,
  • the script runs,
  • DOMContentLoaded fires.

An example of the difference

html
<!-- Without defer --> <script src="slow.js"></script> <p>Hello!</p> <!-- With defer --> <script src="slow.js" defer></script> <p>Hello!</p>

With defer the text "Hello!" appears immediately while the script is still downloading. Without defer the page freezes until slow.js has loaded.

When there are several scripts

html
<script src="a.js" defer></script> <script src="b.js" defer></script>

Scripts with defer:

  • download in parallel;
  • but execute in the order they appear in the HTML, that is a.js first, then b.js.

This is the main difference from async, where the order is not guaranteed: whichever file arrives over the network first runs first. That is why only defer is suitable for pieces of code that depend on each other.

How defer differs from async

AttributeWhen it downloadsWhen it executesGuaranteed orderTypical use
(no attribute)Immediately when metImmediately (blocks HTML)YesLegacy JS in <head>
asyncIn parallel with HTMLRight after download (does not wait for the DOM)NoAnalytics, ads, widgets
deferIn parallel with HTMLAfter the DOM is builtYesThe main application scripts

The overall table:

PropertyNo attributeasyncdefer
Downloads the scriptImmediately (blocks HTML)In parallelIn parallel
Executes the scriptRight after downloadRight after downloadAfter HTML parsing
Execution orderIn orderArbitraryIn order
Blocks HTMLYesNoNo
Waits for the DOMNoNoYes

Compatibility and the effect on DOMContentLoaded

defer works in all modern browsers. Putting the script in <head> is the most correct way to include JS:

html
<head> <script src="main.js" defer></script> </head>

The browser does not block page loading and guarantees that your code runs when the DOM is already there.

DOMContentLoaded fires after all defer scripts have executed. So inside a handler for that event you can be sure your scripts have already run and registered everything they needed to.

It is also worth remembering that <script type="module"> behaves like defer by default, so for modules the attribute does not need to be written at all.

Common mistakes

  • Putting defer on an inline script. Without src the attribute is simply ignored and the code runs immediately.
  • Expecting an execution order from async. If b.js depends on a.js, async breaks it with a random order; you need defer.
  • Thinking defer delays the download. The file is fetched right away and in parallel, only execution is postponed.
  • Deferring code that must run as early as possible. An anti-flicker theme script, for instance, is better left as a synchronous inline snippet.
  • Mixing defer with scripts that call document.write(). In a deferred script that call is simply ignored.
  • Still keeping scripts at the end of <body> "because it is faster". With defer in <head> the browser starts downloading earlier and the result is better.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.