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. deferdownloads 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
deferscripts run in the order they appear in the markup; withasyncthe order is arbitrary. deferin<head>is the standard way to include the main application scripts.deferonly applies to external scripts withsrc; for inline code it is ignored.
Quick example
<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
<script src="app.js"></script>What happens:
- The browser starts loading the HTML.
- It meets a
<script>. - It stops parsing the HTML in order to:
- download
app.js, - execute it.
- download
- 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
<script src="app.js" defer></script>Now the browser:
- Starts loading HTML and JS in parallel.
- Does not block HTML parsing.
- Executes
app.jsonly after the DOM is fully built, but before theDOMContentLoadedevent.
So the sequence is:
- the HTML is parsed,
- the script downloads in the background,
- the DOM is ready,
- the script runs,
DOMContentLoadedfires.
An example of the difference
<!-- 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
<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.jsfirst, thenb.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
| Attribute | When it downloads | When it executes | Guaranteed order | Typical use |
|---|---|---|---|---|
| (no attribute) | Immediately when met | Immediately (blocks HTML) | Yes | Legacy JS in <head> |
| async | In parallel with HTML | Right after download (does not wait for the DOM) | No | Analytics, ads, widgets |
| defer | In parallel with HTML | After the DOM is built | Yes | The main application scripts |
The overall table:
| Property | No attribute | async | defer |
|---|---|---|---|
| Downloads the script | Immediately (blocks HTML) | In parallel | In parallel |
| Executes the script | Right after download | Right after download | After HTML parsing |
| Execution order | In order | Arbitrary | In order |
| Blocks HTML | Yes | No | No |
| Waits for the DOM | No | No | Yes |
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:
<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
deferon an inline script. Withoutsrcthe attribute is simply ignored and the code runs immediately. - Expecting an execution order from
async. Ifb.jsdepends ona.js,asyncbreaks it with a random order; you needdefer. - Thinking
deferdelays 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
deferwith scripts that calldocument.write(). In a deferred script that call is simply ignored. - Still keeping scripts at the end of
<body>"because it is faster". Withdeferin<head>the browser starts downloading earlier and the result is better.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.