Defer vs Async JavaScript Loading: Which Attribute to Use

Crawvix Editorial Team · Published November 4, 2026 · Last updated November 4, 2026 · 10 min read

Two timelines showing a script that runs after page reading ends and one that interrupts it midway

A plain script tag in the head of a page makes the browser stop. It downloads the file, runs it, and only then carries on reading your HTML. Everything below that tag, including your headline and hero image, waits. The defer and async attributes exist to stop that wait, and defer vs async JavaScript is one of the most common performance decisions on a small site. Picking the wrong one is also a common way to break a page: a menu that only works sometimes, a "$ is not defined" error, or analytics that fires in the wrong order.

This guide explains the difference in plain terms, with a comparison table, examples using example.com, and a repeatable method for deciding what each script on your page needs. It is written for site owners and developers who can edit templates, or who can at least read a plugin's output. You will finish with a classified list of your scripts, safe edits, and a test routine. It does not promise a score or a speed gain: these attributes change when a script loads, not how much work it does once it runs.

Short answer: Both attributes download an external script in parallel with HTML parsing. defer runs scripts after parsing finishes, in the order they appear in the document. async runs each script as soon as it arrives, in any order. Use defer for scripts that need the DOM or each other, and async for independent ones.

Before you begin

You need to edit the page head (or the theme, tag manager or plugin setting that outputs it), a staging copy, and a list of the pages with the most scripts (home page, a form page, a checkout and any page with a widget). Open the browser console on each and note existing errors first; see Find and Fix JavaScript Console Errors on Page Load. Take a screenshot of how menus, sliders and forms behave now, since a changed loading order is the main risk and the behaviour is what you will compare.

Defer vs async JavaScript loading: how do they change execution?

The HTML Standard and MDN describe three behaviours for a classic script that has a src. The table adds the module and dynamic cases because they change the answer.

Script form Download Runs Fixed order? Holds up HTML parsing?
<script src="a.js"> When the parser reaches it Right after download Yes, document order Yes, during download and run
<script defer src="a.js"> In parallel with parsing After parsing finishes, before DOMContentLoaded Yes, document order No
<script async src="a.js"> In parallel with parsing As soon as it has downloaded No Only while it runs, if it lands mid-parse
<script type="module" src="a.js"> In parallel After parsing, like defer Yes, document order No
<script type="module" async src="a.js"> In parallel As soon as the module and its dependencies are ready No Only while it runs
Script added by JavaScript When inserted As soon as available, unless async is set to false No by default; insertion order if async = false No

Details worth knowing, all from the HTML Standard and MDN:

  • Inline scripts ignore defer and async. Both need a src on classic scripts; MDN says defer must not be used without src and has no effect.
  • Both attributes together: MDN says the element acts as if only async were specified. The HTML Standard allows writing both so old browsers without async support fall back to defer.
  • Modules are deferred by default. The defer attribute has no effect on module scripts.
  • Dynamically inserted scripts have a "force async" flag set initially, so they behave as async until you set script.async = false, which puts them in a list that runs in insertion order.
  • DOMContentLoaded waits for deferred and module scripts and does not wait for async scripts. MDN adds that an async script may run after the event has already fired.

web.dev also notes that an async script can still interrupt parsing while it executes, which is why async is not a free pass for heavy code.

Step-by-step instructions

Step 1: List every script on the page

View the page source and search for <script. Also open DevTools, Network, filter to JS and reload; this includes scripts that other scripts inject, such as tag manager contents and chat widgets. For each, note the URL, who owns it (your code, a plugin, a third party), its size, and where it sits (head or end of body). In Lighthouse, the render-blocking insight (previously an audit) flags a script in the <head> that has neither defer nor async, which gives you a ready-made starting list. For help interpreting that report, see How to Read a Lighthouse Report.

What success looks like: A table of scripts with owner, size, position and a blank "needs" column.

Step 2: Classify what each script needs

Ask three questions about every script.

  1. Does it need the HTML to be there when it runs (it queries elements, attaches menu handlers)?
  2. Does it depend on another script, or does another depend on it (a library and the code that uses it)?
  3. Is it fully independent and safe to run at any moment (a stats pixel, a self-contained widget)?

Answer yes to 1 or 2 and the script needs defer, with the dependent scripts listed in the right order. Answer yes only to 3 and async is a candidate. If it must run before anything is drawn (some A/B testing or consent scripts), leave it as a normal blocking script and accept the cost, or ask the vendor for guidance.

What success looks like: Each script is labelled defer, async, module or leave as is, with a reason.

Step 3: Defer your own scripts and keep their order

MDN's advice is to use defer when scripts need to wait for parsing or depend on other scripts or the DOM, placing the tags in the order you want them to run. Moving a script from the end of the body to the head with defer lets the browser begin downloading it earlier while parsing continues, and it still runs after the document is parsed.

<head>
  <script defer src="https://example.com/js/vendor.js"></script>
  <script defer src="https://example.com/js/menu.js"></script>
  <script defer src="https://example.com/js/forms.js"></script>
</head>

Here menu.js can safely use the library in vendor.js, because deferred scripts run in document order. The classic failure is an inline script that runs immediately, before the deferred library exists:

<script defer src="https://example.com/js/jquery.js"></script>
<script>
  $(".menu").show();   // runs first: "$ is not defined"
</script>

Fix it by moving that code into a deferred file, or wrap it in a DOMContentLoaded handler, which fires after deferred scripts have run:

<script>
  document.addEventListener("DOMContentLoaded", () => {
    $(".menu").show();
  });
</script>

What success looks like: Pages render sooner in the waterfall without console errors, and menus and forms still work.

Step 4: Use async only for independent scripts

async suits scripts that depend on nothing and that nothing depends on. Add it to the tag:

<script async src="https://stats.example.com/tracker.js"></script>

Be careful in three places. Execution order is not fixed, so two async scripts that share state can break at random. An async script can run before or after DOMContentLoaded, so any code in it that waits for that event may never fire; MDN's remedy is to check document.readyState first:

function start() { /* your code */ }
if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", start);
} else {
  start();
}

And analytics: web.dev notes that analytics is often loaded early to avoid losing data and may need a lazy-initialisation pattern rather than a simple attribute change. Follow the vendor's current snippet rather than editing it blind. For heavier third-party tags see Third-Party Scripts and Performance.

What success looks like: Each async script works wherever it lands in the loading order, and the page still behaves when you throttle the network.

Step 5: Decide on modules and dynamically added scripts

If your build tool outputs type="module", it is already deferred; adding defer changes nothing. Add async to a module only if it is independent. For scripts you inject yourself, remember they are async by default; set async = false on each if they must run in order.

const a = document.createElement("script");
a.src = "https://example.com/js/a.js";
a.async = false;            // keeps insertion order
document.head.append(a);

A fetchpriority hint on a script (high, low or auto) can nudge download priority relative to other external scripts; MDN calls it a hint, so test it. To fetch a script earlier without running it, see Preload vs Preconnect: Resource Hints and When to Use Each.

What success looks like: Injected scripts that depend on each other run in the right order, every time.

Step 6: Test the effect and look for regressions

Reload each page with the cache disabled and compare three things before and after: console errors, the Network waterfall and your key metrics. Throttle to a slow mobile profile once. Click through menus, search, forms, cookie banners and any payment step. Check that analytics events still arrive. Run Lighthouse again; the render-blocking item should shrink or disappear for scripts you changed. Do not expect an exact amount, and treat one run as noisy. Then compare field data later if you have it (see PageSpeed Insights Explained).

Remember what these attributes do not do. They move work later; they do not make the script smaller or its code cheaper to run, and heavy scripts can still delay interactions after load. For that, see Reduce JavaScript Bundle Size and Improve Interaction to Next Paint (INP) on JavaScript Sites. The wider picture of what blocks rendering is in Remove Render-Blocking CSS and JavaScript Safely.

What success looks like: No new errors, working interactions on every tested page, and a measured change you can explain.

Final verification checklist

  • For your defer vs async JavaScript decisions, every script on the key pages is listed with its owner and need.
  • Scripts that touch the DOM or each other use defer and are in the right order.
  • Only independent scripts use async.
  • No inline code runs before the deferred library it depends on.
  • Code waiting for DOMContentLoaded is safe if the event has already fired.
  • Module scripts are not given a redundant defer.
  • Dynamically added scripts that depend on each other set async = false.
  • The console shows no new errors and all interactions work on a slow-network test.
  • Lighthouse or DevTools shows the changed scripts are no longer parser-blocking.

Common mistakes to avoid

  • Putting async on everything. Order is not guaranteed. Dependent scripts need defer.
  • Adding defer to an inline script. It has no effect; move the code to a file or wrap it.
  • Writing both attributes and expecting defer. The element acts as if only async were set in current browsers, with defer as a fallback only for browsers that lack async.
  • Moving a script to the head without defer. That blocks parsing earlier than before.
  • Deferring a script that must run before first paint. Consent or A/B scripts may need to stay blocking; test the flicker.
  • Editing a vendor snippet without reading its guidance. Analytics and tag managers often have their own loading pattern.
  • Judging success from the score alone. Check behaviour and error logs as well.

Frequently asked questions

Which is faster, defer or async?

Neither is always faster. Both avoid blocking the parser during download. Async can run a script earlier, while defer waits until parsing is done. The result depends on your page, so measure it.

Does defer run before DOMContentLoaded?

Yes. MDN says deferred scripts run after the document is parsed and before DOMContentLoaded, and that the event waits for them to finish. Async scripts are not waited for.

Can I use defer and async together?

You can write both, and the HTML Standard permits it for older browsers, but current browsers treat the script as async. Pick one on purpose.

Is placing scripts at the bottom of the body still needed?

Not usually for external scripts. With defer, the file downloads while parsing continues and runs after the document is parsed, so the head is a common place for it. Both options are valid; test either on your site.

Do defer and async help SEO?

This guide cannot promise that, and no technical change should be expected to move rankings by itself. These attributes can reduce the time a script holds up the page, which may help loading metrics. A script that breaks content you want people and crawlers to see is a risk, so test the rendered page.

Official references


Next step: Try the free website sample to see how your own site does on a few of these checks. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.

In this category

All 38 Performance guides