defer vs async in a script tag
defer and async are <script> attributes that remove the blocking of HTML parsing: both download the file in parallel with the markup, but defer runs the scripts after the DOM is built and in declaration order, while async runs each script immediately after it downloads, in an arbitrary order. That moment of execution is the entire difference between them.
Theory
TL;DR
- With no attributes,
<script src>halts HTML parsing, which blocks rendering. defer: parallel download, execution after the HTML is fully parsed, order preserved.async: parallel download, execution right after the download, order not guaranteed.deferscripts run beforeDOMContentLoadedand the event waits for them;asyncdoes not.deferis for the main site logic;asyncis for independent third-party scripts.- Both attributes are ignored on inline scripts that have no
src.
Quick example
<!-- HTML is being parsed... -->
<script src="main.js" defer></script>
<script src="analytics.js" async></script>| Step | What happens |
|---|---|
| 1 | The browser starts parsing HTML |
| 2 | It downloads both scripts at the same time |
| 3 | analytics.js (async) downloads faster, so it runs immediately |
| 4 | The HTML finishes parsing |
| 5 | Then main.js (defer) runs |
| 6 | Then DOMContentLoaded fires |
The problem defer and async solve
When the browser meets this tag:
<script src="script.js"></script>by default it:
- Stops parsing HTML;
- Downloads
script.js; - Executes it;
- And only then carries on building the DOM.
This blocks page rendering, so the user sees a blank screen until the JS has loaded. That is exactly why the defer and async attributes appeared: to avoid blocking HTML parsing.
How defer works
<script src="script.js" defer></script>- The script downloads in parallel with the HTML, asynchronously.
- But it executes only after the HTML is fully parsed, that is when the DOM is ready, and before the
DOMContentLoadedevent. - Scripts with
deferexecute in the order in which they appear in the HTML.
This is an excellent fit for the main application scripts, for example main.js or vendor.js.
<script src="a.js" defer></script>
<script src="b.js" defer></script>Both download in parallel first, but they execute strictly in sequence: a.js first, then b.js, and only once the DOM has been built. That makes defer safe for code that touches page elements straight away and for files that depend on one another.
How async works
<script src="script.js" async></script>- The script downloads in parallel with the HTML, just as with
defer. - But it executes as soon as it has downloaded, even if the HTML is not fully parsed yet.
- Scripts with
asyncexecute in an unpredictable order, depending on which one downloads first.
It suits independent scripts, for example analytics or advertising.
<script src="a.js" async></script>
<script src="b.js" async></script>Here b.js may run before a.js if it downloaded faster, even though it sits lower in the HTML. On top of that, running an async script interrupts HTML parsing at the moment the file arrives, so a heavy async script can still slow rendering down.
Comparison
| Property | defer | async |
|---|---|---|
| Script download | Asynchronous, in parallel with HTML | Asynchronous |
| Execution | After the HTML is fully parsed | Immediately after the download |
| Execution order | Preserved | Not guaranteed |
| Blocks HTML parsing | No | Not while downloading, but yes while executing |
The DOMContentLoaded event | Waits for all defer scripts | Does not wait |
| Typical scenarios | The main site logic | External trackers, advertising |
What to choose in practice
| Option | When to use it |
|---|---|
defer | For the main JS code that depends on the DOM, for example interface initialisation |
async | For independent scripts: analytics, metrics, advertising |
| No attribute | Only when you genuinely need to block HTML parsing, which is rare |
A tip: if you use a modern bundler (Next.js, Vite, Webpack), it adds defer to the <script> in head for you, so in modern applications this is already the default behaviour. It is also worth remembering that <script type="module"> behaves like defer automatically, and to get async behaviour for a module you have to state the attribute explicitly.
Common mistakes
- Putting
deferorasyncon an inline script. Without asrcboth attributes are ignored and the script runs synchronously in place. - Relying on order with
async. Ifb.jsneeds variables froma.js,asyncbreaks that randomly and only on a slow network. - Using
asyncfor code that works with the DOM. The script can run before the element it needs exists in the document, andquerySelectorreturnsnull. - Confusing "does not block parsing" with "does not block at all". An
asyncscript still occupies the main thread while it executes. - Expecting
DOMContentLoadedto wait forasync. The event can fire before such a script, so a listener registered inside anasyncfile sometimes simply arrives too late. - Leaving
document.write()in a deferred script. After the document has been parsed it wipes the page, so it must not be used withasyncordefer.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.