Suggest an editImprove this articleRefine the answer for “Difference between script, async and defer”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`script`, `async`, and `defer`** control when the browser fetches and runs JavaScript relative to HTML parsing. Plain `<script>` blocks the parser. Both `async` and `defer` fetch in parallel, but `async` executes immediately on load while `defer` waits until the DOM is fully parsed. ```html <script src="app.js"></script> <!-- blocks parser, runs immediately --> <script src="track.js" async></script> <!-- parallel fetch, no order guarantee --> <script src="app.js" defer></script> <!-- parallel fetch, runs after parse, in order --> ``` **Key:** `defer` for app scripts, `async` for independent tools like analytics.Shown above the full answer for quick recall.Answer (EN)Image**`script`, `async`, and `defer`** control when the browser fetches and runs JavaScript relative to HTML parsing. ## Theory ### TL;DR - Plain `<script>` stops the HTML parser, fetches the file, executes it, then continues. Blocking. - `async` fetches in parallel and runs the script as soon as the download finishes. No order guarantee. - `defer` fetches in parallel but waits until the full HTML is parsed, then runs scripts in declared order. - Analogy: the HTML parser is a chef reading a recipe. Plain `<script>` makes them stop chopping and handle the task right now. `async` hands it to an assistant who can interrupt at any moment. `defer` hands it off and the assistant steps in only after the chef reads the whole recipe, in sequence. - Decision rule: `defer` for app scripts, `async` for independent third-party tools like analytics. ### Quick example ```html <!DOCTYPE html> <html> <head> <!-- Blocks parser until fetched + executed --> <script src="slow.js"></script> <!-- Fetches in parallel, runs whenever ready (no order guarantee) --> <script src="analytics.js" async></script> <!-- Fetches in parallel, runs after full parse, in declared order --> <script src="app.js" defer></script> </head> <body> <div id="root"></div> </body> </html> ``` If `slow.js` takes 2 seconds to download, `async` and `defer` don't hold up the parser. Plain script does. ### Key difference The real split is when execution happens. Both `async` and `defer` use the browser's preload scanner to fetch files without blocking the parser. But `async` runs the script the moment its download finishes, which could be mid-parse. At that point the DOM is incomplete. `defer` puts the script into an ordered callback list and drains that list only after the parser finishes, before `DOMContentLoaded` fires. ### When to use - **Main app bundle (React, Vue, vanilla JS)** - use `defer`. DOM is ready, order is preserved. - **Analytics and ads (Google Analytics, Facebook Pixel)** - use `async`. They don't depend on page elements. - **Multiple scripts with dependencies (jQuery + plugin)** - use `defer` on both. Declared order is respected. - **Small inline critical code** - plain `<script>` in `<head>`. No fetch needed, fast enough. - **Plain script at body bottom** - acceptable for small pages, but `defer` in `<head>` is cleaner. ### Comparison table | Aspect | `<script>` | `async` | `defer` | |---|---|---|---| | Blocks HTML parsing | Yes (fetch + exec) | No (fetch), briefly (exec) | No | | Fetch | Synchronous | Parallel | Parallel | | Execution timing | Immediately on encounter | When download finishes | After full HTML parse | | Order guarantee | N/A | No | Yes | | DOM access | Only above the tag | Unreliable (may be mid-parse) | Full | | Best for | Small inline, end-of-body | Independent (analytics, ads) | App scripts, libraries | ### How the browser handles this When the parser hits a plain `<script>`, it stops, hands the URL to the network layer, waits, then runs the script in the main thread. `async` and `defer` both get picked up by the preload scanner, a separate speculative pass that reads ahead in the HTML stream without building the DOM. The difference is the queue. `async` scripts execute off the async queue when their download finishes. `defer` scripts go into a per-document deferred list and run after `document.readyState` becomes `"interactive"`, before `DOMContentLoaded`. This one trips people up regularly: `defer` is silently ignored on inline scripts without a `src` attribute. The HTML spec says so. Inline code with `defer` runs immediately, same as plain `<script>`. ### Common mistakes **Using `async` for scripts that depend on each other** ```html <!-- Wrong: initMap() may run before maps.js is ready --> <script async src="maps.js"></script> <script async src="app.js"></script> <!-- app.js calls initMap() --> <!-- Fix: defer preserves declared order --> <script defer src="maps.js"></script> <script defer src="app.js"></script> ``` `async` turns each script into a race. If `app.js` downloads first, `initMap` is not defined yet. No error, just silence. **Adding `defer` to an inline script** ```html <!-- Wrong: defer is ignored here, runs immediately --> <script defer> document.getElementById('root').innerHTML = 'Hello'; </script> <!-- Fix: move to external file or wrap in DOMContentLoaded --> <script> document.addEventListener('DOMContentLoaded', () => { document.getElementById('root').innerHTML = 'Hello'; }); </script> ``` **Combining `async` with a `DOMContentLoaded` listener inside the script** ```html <script async src="app.js"></script> <!-- app.js: document.addEventListener('DOMContentLoaded', init) --> ``` If `app.js` downloads after `DOMContentLoaded` already fired, `init` never runs. No error is thrown. Use `defer` instead. Deferred scripts run before `DOMContentLoaded`, so the listener always registers in time. **Blocking `<script>` in `<head>` for performance reasons** A render-blocking script in `<head>` delays First Contentful Paint. Even a 100ms network request is enough to push FCP noticeably in Lighthouse. Move it to body bottom or add `defer`. ### Real-world usage - React (Vite / CRA output) - `defer` on the bundle; DOM is ready when `ReactDOM.createRoot` runs. - Google Analytics (gtag.js) - `async` in `<head>`; tracking pixel with no DOM dependency. - jQuery + Bootstrap plugins - `defer` on both; jQuery loads first, plugin finds it. - Facebook Pixel - `async`; fires events independently. - Webpack's `html-webpack-plugin` - injects `defer` by default on output chunks, which is why most React apps just work without developers thinking about this. ### Follow-up questions **Q:** What happens if you add both `async` and `defer` to the same script? **A:** `async` wins. Browsers treat it as async and ignore `defer` when both are present. **Q:** Does `defer` guarantee execution before `DOMContentLoaded`? **A:** Yes. Deferred scripts run after full HTML parse and before `DOMContentLoaded` fires. That is the contract the spec defines. **Q:** Can inline scripts use `defer`? **A:** No. `defer` has no effect on scripts without a `src`. They execute immediately regardless of the attribute. **Q:** How does the preload scanner detect `async` and `defer` before the parser reaches those tags? **A:** The preload scanner is a secondary tokenizer that reads the HTML stream without building the DOM. It finds `src` attributes on `<script async>` and `<script defer>` early and queues the fetches at lower priority, so the download is already in flight by the time the main parser gets there. **Q:** How do `type="module"` scripts behave compared to `defer`? **A:** Module scripts behave like `defer` by default. They fetch in parallel and execute after parse, in order. Adding `async` to a module script makes it run as soon as it is ready, same as a regular async script. ## Examples ### Blocking script delays page render ```html <!DOCTYPE html> <html> <head> <title>Blocking example</title> <!-- Fetches over the network before any body content renders --> <script src="heavy-analytics.js"></script> </head> <body> <h1>Users see this later than they should</h1> </body> </html> ``` The `<h1>` is not painted until `heavy-analytics.js` downloads and runs. Moving it to `async` or placing it at body bottom removes the block entirely. ### React app with deferred scripts ```html <!DOCTYPE html> <html> <head> <title>App</title> <!-- All three fetch in parallel, but execute in declared order --> <script defer src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script defer src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script defer src="bundle.js"></script> </head> <body> <div id="root"></div> <!-- By the time bundle.js runs, #root exists and React is loaded --> </body> </html> ``` `defer` preserves the order: React, then ReactDOM, then the app bundle. The `#root` div is in the DOM when `ReactDOM.createRoot` runs. No `setTimeout` hacks or `readyState` checks needed. ### async mid-parse race condition ```html <!DOCTYPE html> <html> <head> <!-- async runs when ready, potentially before <body> is parsed --> <script async src="tracker.js"></script> </head> <body> <form id="signup">...</form> <!-- May not exist when tracker.js executes --> </body> </html> ``` If `tracker.js` calls `document.getElementById('signup')`, it may return `null`. The form has not been parsed yet. `defer` removes this uncertainty: by execution time, the full DOM is available.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.