Suggest an editImprove this articleRefine the answer for “What is the difference between async and defer?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**async vs defer** - both download scripts in parallel with HTML parsing, but execute at different times. ```html <script src="analytics.js" async></script> <!-- executes on download, any order --> <script src="app.js" defer></script> <!-- executes after parsing, in order --> ``` **Key:** `async` for independent third-party scripts; `defer` for app code and anything with dependencies.Shown above the full answer for quick recall.Answer (EN)Image**async vs defer** - two HTML script attributes that both download scripts in parallel with HTML parsing, but differ in when execution happens: `async` runs the moment the download finishes, `defer` waits until the HTML parser is done. ## Theory ### TL;DR - `async` is like ordering food and eating it the second it arrives, no matter what else is happening at the table - `defer` is like finishing your main course first, then eating dessert in the order you ordered it - Main difference: `async` can interrupt HTML parsing to execute; `defer` never does - Use `defer` for scripts that touch the DOM or depend on other scripts. Use `async` for independent third-party scripts like analytics ### Quick example ```html <!-- Blocks HTML parsing: downloads AND executes before parser continues --> <script src="app.js"></script> <!-- Downloads in parallel, executes immediately when ready (may interrupt parsing) --> <script src="analytics.js" async></script> <!-- Downloads in parallel, executes after HTML parsing, order preserved --> <script src="jquery.js" defer></script> <script src="app.js" defer></script> ``` Both `async` and `defer` start downloading immediately and let the HTML parser keep working. The difference shows up at execution time. ### Key difference `async` executes the moment the script finishes downloading. If parsing is still in progress, the browser pauses it, runs the script, then resumes. Two `async` scripts can execute in any order depending on which downloads faster. `defer` queues the script and only runs it after the HTML parser finishes, in document order. That makes `defer` predictable for anything that depends on other scripts or on the DOM existing. ### When to use - `defer` - scripts that manipulate the DOM, depend on other scripts (React bundles, jQuery plugins, framework init code, analytics that reads page content) - `async` - third-party scripts that don't depend on your code and don't need the DOM (Google Analytics, Sentry, ad networks, chat widgets) - Neither - inline scripts (`defer` is ignored on inline code), or ES modules (`type="module"` is already deferred by spec) ### Comparison table | Aspect | Default `<script>` | `async` | `defer` | |---|---|---|---| | Download | Blocks parsing | Parallel | Parallel | | Execution | Blocks parsing | Immediate (may interrupt) | After parsing | | Order preserved | N/A | No | Yes | | DOM ready at execution | No | No | Yes | | Best for | Critical inline code | Independent third-party | App code, dependencies | | When to use | Rare | Analytics, ads, tracking | 95% of your scripts | ### How the browser handles this When the HTML parser hits a `<script>` tag, it enters a decision point. With no attribute, it stops, downloads, executes, then continues. With `async`, it spawns a network request and keeps parsing, but the moment that script arrives, parsing stops and the script executes. With `defer`, it spawns the same parallel request but only executes after the parser finishes, right before the `DOMContentLoaded` event fires. Deferred scripts run in document order; async scripts run in download-completion order, which is non-deterministic. One thing I have seen trip up teams: they assume `async` is always faster. For independent scripts, it is. But if your script needs the DOM, `async` can execute it before the expected elements exist. You get runtime errors that appear and disappear based on network speed, which makes them frustrating to reproduce. ### Common mistakes **Mistake 1: Using `async` for dependent scripts** ```html <!-- Wrong: jquery-plugin.js might execute before jquery.js --> <script src="jquery.js" async></script> <script src="jquery-plugin.js" async></script> <!-- Fix: defer preserves order --> <script src="jquery.js" defer></script> <script src="jquery-plugin.js" defer></script> ``` If `jquery-plugin.js` downloads faster, it executes first. You get `$ is not defined` at runtime, and the bug disappears on slower connections. Hard to reproduce, harder to explain to a client. **Mistake 2: Both `async` and `defer` on the same script** ```html <!-- Wrong: async takes precedence, defer is silently ignored --> <script src="app.js" async defer></script> <!-- Fix: pick one --> <script src="app.js" defer></script> ``` The browser treats `async defer` as just `async`. This combination was a fallback for very old browsers that did not support `async`, but those browsers are gone. Avoid it today. **Mistake 3: Adding `defer` to inline scripts** ```html <!-- Wrong: defer has no effect on inline scripts --> <script defer> console.log(document.getElementById('root')); // Still null </script> <!-- Fix: move to an external file --> <script src="app.js" defer></script> ``` The spec says `defer` only applies to external scripts with a `src` attribute. Inline scripts ignore it entirely and execute immediately. **Mistake 4: Adding `defer` to module scripts** ```html <!-- Redundant: type="module" is already deferred by spec --> <script type="module" src="app.js" defer></script> <!-- Same behavior, defer attribute unnecessary --> <script type="module" src="app.js"></script> ``` **Mistake 5: Expecting `DOMContentLoaded` to fire inside a deferred script** Deferred scripts run *before* `DOMContentLoaded` fires, not after. The DOM is ready, but the event has not been dispatched yet. If your deferred script listens for `DOMContentLoaded`, that handler will never fire because the event already passed by the time the listener registers. ### Real-world usage - React / Vue / Angular bundles - `defer` (Next.js, Nuxt, Angular CLI all default to this) - Google Analytics - `async` (independent, does not need the DOM) - Sentry error tracking - `async` (captures errors independently) - jQuery + plugins - `defer` on both (preserves load order) - Stripe / PayPal checkout - `async` (third-party, self-contained) - Custom analytics that reads page content - `defer` (needs DOM to exist) ### Follow-up questions **Q:** What happens if a deferred script throws an error? **A:** That script stops executing, but subsequent deferred scripts still run. The page does not break by itself, but any script that depended on the errored one may fail silently. **Q:** What is the difference between `defer` and putting scripts at the end of `</body>`? **A:** Functionally similar for execution order. But `defer` in `<head>` starts downloading earlier, during HTML parsing, so the script is ready sooner. Putting scripts at the end of body was the old workaround before `defer` was well-supported. Today, `defer` in `<head>` is the correct pattern. **Q:** Can you use `async` with `type="module"`? **A:** Yes. But modules are already deferred by default, so adding `async` makes them execute immediately on download like classic async scripts. That breaks the dependency order modules normally preserve. Rarely useful in practice. **Q:** You have three scripts: A depends on B, B is independent, C depends on A. How do you load them optimally? **A:** Load B with `async` since it has no dependencies. Load A and C with `defer` to guarantee A runs before C. B downloads in parallel and executes whenever ready; A and C execute in document order after parsing. If B is actually required by A, use `defer` for all three to be safe. ## Examples ### Basic: async interrupting DOM access ```html <head> <script src="analytics.js" async></script> </head> <body> <div id="app">Loading...</div> </body> ``` ```javascript // analytics.js // This might run before <div id="app"> is parsed const app = document.getElementById('app'); console.log(app); // null if HTML is not fully parsed yet ``` `analytics.js` starts downloading the moment the browser sees the `<script>` tag. On a fast connection it can finish downloading before the body is parsed, so `getElementById` returns `null`. Switching to `defer` makes the read safe because execution waits for parsing to finish. ### Intermediate: React initialization with defer ```html <head> <script src="react.js" defer></script> <script src="react-dom.js" defer></script> <script src="app.js" defer></script> </head> <body> <div id="root"></div> </body> ``` ```javascript // app.js - runs after HTML parsing, after react.js and react-dom.js const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />); ``` All three scripts download in parallel during HTML parsing. They execute in order: `react.js` first, then `react-dom.js`, then `app.js`. By the time `app.js` runs, `ReactDOM` is available and `#root` exists in the DOM. This is exactly what Next.js generates by default for every page bundle. ### Advanced: Race condition with async ```javascript // The bug: two async scripts with a dependency // <script src="jquery.js" async></script> // <script src="jquery-plugin.js" async></script> // Timeline on a fast CDN connection: // t=0ms: both scripts start downloading // t=40ms: jquery-plugin.js arrives (smaller file) -> executes -> ERROR: $ is not defined // t=80ms: jquery.js arrives -> executes, but plugin already failed // Timeline on a slow connection: // t=0ms: both scripts start downloading // t=200ms: jquery.js arrives first due to network reordering -> executes // t=250ms: jquery-plugin.js arrives -> $ exists -> works perfectly // The bug only appears on fast connections. Passes local testing, breaks on CDN. // Fix: use defer to guarantee order // <script src="jquery.js" defer></script> // <script src="jquery-plugin.js" defer></script> // Analytics can still be async because it has no dependencies // <script src="google-analytics.js" async></script> ``` This is the kind of bug that passes every test locally and breaks in production at scale. Async execution order depends on which file bytes arrive first, which changes based on network conditions, CDN edge location, and file size. `defer` removes that variable entirely.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.