skip to content

In HTML, how do a plain <script src>, a <script defer src>, and a <script async src> differ in when the file is downloaded, when it executes, and whether execution order is guaranteed?

level: juniorimportance: must knowfreq 85%

answer

  1. three tags, three different timings
  2. one attribute keeps document order
  3. download in parallel is not the difference
  4. defer waits for the parser; async does not

basics

~20 s

A plain <script src> pauses HTML parsing to download and run. defer downloads in parallel and runs after parsing, in document order. async downloads in parallel and runs the moment it arrives, in unpredictable order.

solid answer

~50 s

A classic `<script src>` with no attributes is parser-blocking: the parser stops at the tag, fetches the file, executes it, then resumes — which is why an unattributed script in `<head>` delays content appearing. `defer` tells the browser to fetch the file in parallel with parsing and execute it only once the document has been fully parsed; deferred scripts run in the order they appear in the markup, and they all run before the `DOMContentLoaded` event fires. `async` also fetches in parallel, but executes as soon as that particular download finishes, interrupting the parser wherever it happens to be — so the order between several async scripts is whatever the network delivers, and an async script may run before or after `DOMContentLoaded`. Both attributes apply only to scripts that have `src`; on an inline classic script they do nothing. Use `defer` for your own application code, `async` for independent third-party snippets such as analytics.

code

html · 9 lines
html
<!-- blocks parsing at this point -->
<script src="/blocking.js"></script>

<!-- fetched in parallel, executed after parsing, in this order -->
<script defer src="/lib.js"></script>
<script defer src="/app.js"></script>

<!-- fetched in parallel, executed whenever it arrives -->
<script async src="/analytics.js"></script>

go deeper

for a junior

Be able to state the three behaviours plainly: no attribute blocks parsing, defer runs after the HTML is parsed in document order, async runs whenever it arrives. Say which one you would put on your app bundle.

for a middle

Explain the mechanics: the parser pauses at a blocking script, both attributes fetch in parallel, and only defer preserves order and lands before DOMContentLoaded. Be ready to say that inline classic scripts ignore both attributes.

for a senior

Show the production judgment. Decide per script whether ordering matters, explain why an async library plus async consumer is a race that passes locally and fails on cold networks, and note that neither attribute stops the script from blocking the main thread once it executes.

for a principal

Own the policy: which script tags a team is allowed to add, how third-party tags are constrained to async so a vendor outage cannot delay the page, and how you enforce that in review or with a template so ad-hoc blocking scripts never creep back into head.

## What the parser is doing when it meets a script The HTML parser reads the document top to bottom, turning bytes into tokens and tokens into DOM nodes. A `<script>` element is special: by default the parser cannot continue past it, because the script is allowed to write into the document and to inspect the DOM built so far. So for a classic script with a `src` and no loading attribute, the parser stops, the browser fetches the file over the network, the script is executed to completion, and only then does parsing resume. Everything below that tag — text, images, the rest of the page — does not exist in the DOM yet and is not rendered. This is why "scripts at the bottom of the body" became folklore: putting the tag last means there is nothing left to block. Modern pages do not need that trick, because two attributes let you keep the tag in `<head>` where the browser discovers it early. ## defer ```html <script defer src="/app.js"></script> ``` `defer` splits download from execution. The browser starts fetching `/app.js` immediately, in parallel with parsing, and never pauses the parser for it. When the document has been completely parsed, the browser executes all deferred scripts **in the order they appeared in the markup**, and then fires `DOMContentLoaded`. Two consequences matter in interviews: - Order is guaranteed. If `lib.js` is declared before `app.js` and both are deferred, `lib.js` runs first, even if it downloads second. - The DOM is complete when the code runs. You do not need to wrap deferred code in a `DOMContentLoaded` listener to query elements — though doing so is harmless. `defer` is only meaningful on an external classic script. On an inline `<script>` with no `src`, the attribute is ignored and the code runs immediately, blocking the parser. ## async ```html <script async src="https://cdn.example.com/analytics.js"></script> ``` `async` also fetches in parallel, but it gives up all ordering. As soon as the file lands, the browser executes it — pausing whatever the parser was doing at that instant. Three async scripts execute in completion order, which varies with file size, cache state and network conditions. An async script can run before the document is parsed (so `document.body` may be incomplete or absent), or after `DOMContentLoaded` has already fired, if the download was slow. That makes `async` correct only for code that depends on nothing and that nothing depends on: analytics beacons, error reporters, ad tags. The classic bug is marking a library and the code that uses it as `async`; sometimes it works locally with a warm cache, and fails in production when the library arrives second. ## Putting the three side by side | Form | Download | Execution | Order preserved | |---|---|---|---| | `<script src>` | blocks parsing | immediately, before parsing continues | yes | | `<script defer src>` | parallel | after parsing, before `DOMContentLoaded` | yes | | `<script async src>` | parallel | as soon as it arrives, parser paused | no | ## Details people miss - If both attributes are present on a classic script, `async` wins in browsers that support it; `defer` is the fallback for engines that only understand it. - `<script type="module">` is deferred by default, so a module tag in `<head>` already behaves like `defer` without saying so. - Neither attribute makes the script run off the main thread. Execution is still single-threaded on the main thread; the parallelism is in the network fetch only. A candidate who says `async` "runs the script in the background" has the wrong mental model. - `defer` does not delay the download. This is the single most common misstatement: people describe `defer` as "download later", when it is "download now, run later". - Because the parser discovers a `<head>` script earlier than a `</body>` one, a deferred head script usually finishes downloading sooner than the same file placed last in the body. ## How to answer in an interview Name the three timings, name which one preserves order, and finish with the decision rule: `defer` for first-party application code that touches the DOM or has dependencies; `async` for self-contained third-party code where arriving early matters more than ordering; unattributed classic scripts only when you deliberately need the code to run before the rest of the document parses, which is rare.

  • If a deferred script and an async script are both declared in the head, which one can you rely on to see the full DOM?
    The deferred one. Deferred scripts execute only after the document has been completely parsed, so every element is in the DOM. An async script executes whenever its download finishes, which can be mid-parse — elements below the current insertion point may not exist yet, so it must guard its DOM access or listen for `DOMContentLoaded`.
  • What happens if you put both async and defer on the same external classic script?
    Browsers that support `async` honour `async` and ignore `defer`; `defer` exists there purely as a fallback for engines that never implemented `async`. Since every current browser supports both, writing them together today just means the script behaves as `async` — so if you actually wanted ordered execution, remove `async`.
  • Does async or defer move script execution off the main thread?
    No. Both attributes only change fetch and execution scheduling; the JavaScript still runs on the main thread and still blocks rendering while it runs. A large async bundle can freeze the page just as badly as a blocking one — it simply freezes it at an unpredictable moment. Moving work off-thread requires a worker, not a loading attribute.

saying these in an interview costs you the question

  • Says defer and async are interchangeable
  • Claims async guarantees the document order
  • Says defer postpones the download, not just execution
  • Thinks async runs the script on another thread
  • Marks a library and its consumer both async

context