Async vs Defer: Speed Up JavaScript Loading


Ever wondered why some websites load at lightning speed while others crawl like a snail? It’s pretty common these days. Building websites has become easier than texting your crush—thanks to modern tools that can spin up an app from just a prompt. But here’s the catch: even after the app is built, there’s still a lot going on behind the scenes. One of the most important factors is speed and optimizations.
When a website loads, the browser first fetches and parses the HTML document. During this process, it also requests the necessary CSS and JavaScript files from the server. Some of these resources are render-blocking—meaning the browser pauses HTML parsing until they’re fully loaded and executed. This delay directly impacts performance, causing lag and slower page loads. In this article, we’ll break down what render-blocking resources are and how to mitigate them using techniques like async
and defer
.
What happens without async/defer
?
Take this example:
<!DOCTYPE html>
<html>
<head>
<title>No async/defer</title>
<script src="./index.mjs"></script>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Here’s what happens step by step:
HTML parsing starts – The browser begins parsing the
HTML
from top to bottom.Script tag found – When it encounters
<script src="./index.mjs"></script>
withoutasync
ordefer
:Parsing is paused immediately.
The browser makes a network request to fetch
index.mjs
.
Script fetched – Once the script is fully downloaded…
Script executed – The browser runs the JavaScript right away (still no rendering is happening yet).
Resume parsing/rendering – Only after the script has finished executing, the browser continues parsing the rest of the HTML and rendering the page.
async
in Action ⚡️
So, what if we add async
in the HTML
script tag? Let’s have a look!
<!DOCTYPE html>
<html>
<head>
<title>No async/defer</title>
<script async src="./index.mjs"></script>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
When the async
attribute is added to a script tag, the script loading and execution flow behaves differently.
HTML parsing starts – The browser begins parsing the
HTML
from top to bottom.Script tag found – When it encounters
<script async src="./index.mjs"></script>
withasync
:Script download starts immediately in parallel (non-blocking fetch).
HTML parsing continues (unlike normal script where parsing pauses).
Script download completes → As soon as the file is ready:
Browser pauses parsing to execute the script right away.
Important ⚠️: This can happen before, during, or after parsing
<body>
depending on network speed.
Parsing resumes → After script execution, parsing continues where it left off.
With async
, the fetch happens in parallel, but the order of script execution is not guaranteed since each script executes immediately once its download is complete, regardless of the HTML parsing state or other scripts.
defer
in Action ⚡️
So, let’s add defer
this time in the HTML
script tag and see what happens
<!DOCTYPE html>
<html>
<head>
<title>No async/defer</title>
<script defer src="./index.mjs"></script>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
So, If we add defer
in the script
tag there’s a significant change in the flow:
HTML parsing starts – The browser begins parsing the HTML from top to bottom.
Script tag found – When it encounters
<script defer src="./index.mjs"></script>
withdefer
:Script download starts immediately in parallel (non-blocking fetch).
Parsing doesn’t pause (unlike the default script).
Parsing continues normally:
- Meanwhile, the script is downloading in the background.
After parsing finishes (DOM is ready):
- All deferred scripts are executed in the order they appear in the document (important difference from
async
, which can fire anytime).
- All deferred scripts are executed in the order they appear in the document (important difference from
DOMContentLoaded event fires only after deferred scripts are executed:
- This guarantees your DOM + deferred scripts are ready together.
So, with defer
as well fetch happens in parallel but the executing happens after the HTML
parsing the order of script execution is guaranteed.
When to use which?
When deciding between async
and defer
, the rule is simple: use async
for scripts that are independent of the DOM and don’t rely on other scripts, such as analytics, ads, or tracking pixels, where execution order doesn’t matter. On the other hand, use defer
for scripts that depend on the DOM being parsed or where execution order matters, such as frameworks, libraries, or any code that manipulates page elements. If you don’t specify either attribute, the script will block HTML
parsing—meaning the browser will stop rendering, fetch the script, execute it, and then resume parsing—which is rarely ideal in modern applications unless the script is tiny and critical to rendering (like a necessary polyfill).
Conclusion
So, we’ve now seen a brief overview of how async
and defer
work, along with how script execution interacts with HTML parsing. While a normal script blocks the parser until it’s fetched and executed, async
allows scripts to load in parallel and execute as soon as they’re ready, and defer
ensures scripts load in parallel but execute only after the DOM has been fully parsed. Choosing between them depends on whether execution order and DOM readiness matter for your scripts.
Subscribe to my newsletter
Read articles from Asif Siddique directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
