async and defer both tell the browser to download a script without pausing the HTML parser, so neither blocks the page from rendering. The difference is when the script runs: defer waits until the HTML is fully parsed and runs scripts in the order they appear, while async runs each script the moment it finishes downloading, in whatever order they arrive. Use defer for your own scripts and anything that depends on another script; use async for independent scripts such as analytics. This guide explains the timing, the edge cases and how to apply it on a real site. It is part of our guide to eliminating render-blocking resources.
Quick answer
- No attribute: the parser stops, the script downloads and runs, then parsing continues. It blocks rendering.
defer: downloads in parallel, runs after the HTML is parsed, in document order, beforeDOMContentLoaded.async: downloads in parallel, runs as soon as it arrives, possibly mid-parse, in no guaranteed order.- Use
deferfor your own scripts, libraries and plugins that depend on each other (jQuery and its plugins). - Use
asyncfor scripts that depend on nothing and that nothing depends on: analytics, tag managers, some ad and widget loaders. - Modules (
type="module") are deferred by default. Inline scripts ignore both attributes.
What happens without async or defer
A plain <script src="…"> in the <head> is parser-blocking. When the browser reaches it, it stops reading the HTML, downloads the file, runs it, and only then continues. Nothing below the tag can be rendered until that is done. With three such scripts in the head, the visitor waits for three downloads and three executions before seeing anything, which shows up as a later First Contentful Paint and Largest Contentful Paint.
The browser's preload scanner does look ahead and start downloading later files early, which softens the cost, but it cannot run them early. The order and the pause stay.
How defer works
<script src="/js/jquery.min.js" defer></script>
<script src="/js/slider.js" defer></script>- The parser sees the tag, starts the download in the background and keeps parsing.
- When the whole document has been parsed, the browser runs the deferred scripts in the order they appear in the HTML, regardless of which download finished first.
- Then the
DOMContentLoadedevent fires. It waits for deferred scripts.
Because deferred scripts run after parsing, the whole DOM exists when they run, so they can find any element on the page. Because they keep their order, slider.js can safely use jQuery loaded by the script before it.
defer only works on external scripts, those with a src attribute. On an inline script it is ignored.
How async works
<script src="https://www.googletagmanager.com/gtag/js?id=G-XXXX" async></script>- The parser sees the tag, starts the download and keeps parsing.
- As soon as the file arrives, the parser pauses briefly and the script runs, whether the HTML is finished or not.
- Several async scripts run in the order their downloads finish, which you cannot predict.
DOMContentLoaded does not wait for async scripts. An async script may run before the parser has reached the element it needs, so it should not assume the page is complete.
async vs defer side by side
| No attribute | defer | async | |
|---|---|---|---|
| Blocks parsing while downloading | Yes | No | No |
| When it runs | Immediately | After parsing, before DOMContentLoaded | As soon as it arrives |
| Can interrupt parsing | Yes | No | Yes, briefly |
| Order between scripts | Document order | Document order | Download order |
| DOM available when it runs | Only what is above it | All of it | Unknown |
| Works on inline scripts | n/a | No | No (except modules) |
| Best for | Scripts the first paint truly needs | Your own code, dependencies, libraries | Independent third-party tags |
If a script has both attributes, async wins in every current browser. Old pages sometimes use async defer together, a pattern from the time when some browsers supported only defer; it behaves as async today.
Which one should I use?
Ask two questions about each script:
- Does it depend on another script, or does another script depend on it? Then use
deferon all of them, so their order is kept. jQuery, its plugins and your theme script are the classic case. - Does it need the whole page to exist? Then
defer, which guarantees the DOM is parsed.
If the answer to both is no, async is fine and runs the script a little sooner. Analytics and tag managers are designed for it; their own snippets usually include async.
A few scripts should stay blocking or be rethought:
- A/B testing and personalisation scripts that change the first screen. Deferring them causes a visible flicker as content changes after it appears. Keep them small and early, or do the work on the server.
- Scripts that render the first screen, such as a JavaScript-built hero. Better to send that content as HTML.
- Scripts that use
document.write(). Browsers ignoredocument.write()calls from async and deferred scripts, so the content they write never appears. Replace them; Chrome also blocksdocument.write()of scripts in some conditions.
Modules, inline and injected scripts
Modules. <script type="module" src="app.js"> is deferred by default: it downloads in parallel and runs after parsing, in order. Adding async to a module makes it run as soon as it and its imports are ready. Inline modules can be async too, which is the one case where an attribute affects an inline script.
Inline scripts. <script>…</script> without src runs immediately, and defer and async are ignored. That creates a common bug: an inline snippet that calls jQuery(…) fails after you defer jQuery, because jQuery has not run yet. Fix it by moving the snippet into a file loaded with defer after the library, or by wrapping it:
<script>
document.addEventListener('DOMContentLoaded', function () {
jQuery('.slider').slick(); // runs after all deferred scripts
});
</script>Injected scripts. A script added with JavaScript (document.createElement('script')) is async by default. To keep several injected scripts in order, set script.async = false on each; they still do not block parsing.
What about putting scripts at the end of the body?
Moving scripts just before </body> was the standard advice before defer was widely supported, and it still works: everything above the scripts is parsed and painted first. defer in the <head> is usually better, because the browser discovers and starts downloading the file much earlier, while the rest of the HTML is still arriving, and the order and timing are the same.
What "defer parsing of JavaScript" means
"Defer parsing of JavaScript" is an older speed-test recommendation that means the same thing in practice: stop scripts from blocking the first render by loading them with defer or async, or later. Many WordPress plugins still use the phrase for their "defer JavaScript" setting. It is not about the browser's JavaScript parser itself; the script is still parsed and compiled when it runs.
Some plugins go one step further with "delay JavaScript execution", which loads chosen scripts only after the first user interaction (a scroll, tap or key press). That can help a lot with heavy third-party tags, but it breaks anything that must work without interaction, such as a cookie banner or above-the-fold slider, so test each script you delay.
How to defer JavaScript in WordPress
Since WordPress 6.3, wp_enqueue_script() and wp_register_script() accept a loading strategy, so you do not need to filter the HTML:
// functions.php of a child theme, or a small plugin
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script(
'theme-main',
get_stylesheet_directory_uri() . '/js/main.js',
['jquery'],
'1.0',
['strategy' => 'defer', 'in_footer' => false]
);
});WordPress checks the dependency tree: if a script that depends on yours is still blocking, WordPress quietly loads yours blocking too, so the order stays safe. That is why adding defer to one script sometimes has no visible effect: its dependants need a strategy as well.
For scripts added by plugins you did not write, use a performance plugin's "defer JavaScript" option, or an asset manager to stop them loading on pages that do not use them. The plugin-by-plugin walkthrough is in eliminating render-blocking resources and unused CSS on WordPress.
How to check the result
The unused code checker loads the page in Chromium with code coverage on and lists every script by unused bytes, first-party and third-party apart, with Google's estimate of the time you would save. Deferring fixes when a script runs; this shows which scripts should not load on that page at all. The speed test's render-blocking finding lists any script still blocking the first paint:
After the change, click through the pages that use the scripts: menus, sliders, forms, the cart and checkout. Open the browser console and look for errors such as "jQuery is not defined", which means an inline script now runs before its library. For heavy third-party tags that still cost time after deferring, see third-party scripts: tag managers, chat and ads.
Questions people ask
Is async or defer better for performance?
Both remove the blocking download, so both improve the first paint. defer is the safer default because it keeps script order and runs after the page is parsed, so nothing breaks. async can run a script slightly sooner, which suits independent tags such as analytics, but it can interrupt parsing and scripts run in unpredictable order.
Does defer work on inline scripts?
No. The defer attribute only applies to scripts with a src attribute; on an inline script it is ignored and the code runs immediately. To run inline code after deferred scripts, wrap it in a DOMContentLoaded event listener or move it into a small external file loaded with defer after the library it uses.
Should analytics scripts use async or defer?
Usually async, which is what Google Analytics and Google Tag Manager snippets use. Analytics does not depend on your other scripts, so running it as soon as it arrives is safe, and it starts measuring slightly earlier. If you care more about the first paint than about counting very short visits, defer is also fine.
Does DOMContentLoaded wait for async scripts?
No. DOMContentLoaded fires after the HTML is parsed and all deferred scripts have run, but it does not wait for async scripts. An async script may run before or after the event, depending on when its download finishes. Code that must run after the page is ready should use defer or listen for the event.