JavaScript Loading Strategies: Normal vs Async vs Defer

Hritik Jaiswal
helpshift-engineering
6 min readApr 24, 2024

When it comes to JavaScript loading strategies, it’s all about optimizing how and when your Javascript files are downloaded and executed in the browser. This directly affects your web page’s performance, user experience, and overall efficiency of a web application.

Here’s a breakdown of some common strategies:

  1. Synchronous Loading ( aka Blocking Script )
  2. Asynchronous Loading
  3. Defer Loading
  4. Dynamic Script Loading
  5. Lazy Loading

By choosing the appropriate loading strategy, developers can optimize the loading of JavaScript resources to ensure fast page load times, smooth user interactions, and overall better performance.

Let’s understand the basics

When you load a webpage there are 2 major things happen in your browser:

  1. HTML parsing
  2. Loading of the script (two ways)
    a. Fetching it from the network
    b. Actually executing script line by line

1️⃣ How HTML is parsed with image attributes?

HTML parsing starts from the top and goes on until it ends, if in between it finds any image tag, it will send a request for downloading that image in the background and continue parsing the HTML even if the image is not downloaded.

But this not the case with script tag. In modern websites, scripts are often “heavier” than HTML, their download size is larger, and processing time is also longer.

When the browser loads HTML and comes across a

<script>…</script> 

It can’t continue building the DOM. It must execute the script right now. The same happens for external scripts <script src=”…”></script> the browser must wait for the script to download, execute the downloaded script, and only then can it process the rest of the page.

2️⃣ Normal script ( without any attribute )

<!-- is fetched and run immediately -->
<script src="index.js"></script>
Normal script

Black : HTML Parsing
Green : Fetching JS file from the network
Red : Executing that JS file

When browser is loading a webpage, from the above example. It will parse the html first, then if it encounter a script tag in between, it will stop the parsing of the html at that point and it will fetch the script tag from the network and execute that script then and their itself.

After the script is fully executed, HTML parsing continues where it was stopped earlier.

This is the reason script are added at the end of the body tag. so that normal script does not block rendering of the page.

Example:

Output:

What is DOMContentLoaded ? 🤔

The DOMContentLoaded event fires when non-async scripts have arrived and executed and initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images to finish loading.

non-async scripts : JS code present inside <script>... </script tag.

async script : Using async and defer attribute.

3️⃣ Async (load-first order)

// script might run anytime, before OR after HTML is parsed
<script src="index.js" async></script>
Async (load-first order)

With async attribute, HTML parsing and fetching script from the network is done parallelly, once the async script is downloaded and the script execution starts, HTML parsing is stopped and when script is done with its execution then HTML parsing continues, at the point where it had stopped earlier.

Here, if the script download is in progress, then HTML parsing won’t stop. It will keep parsing the HTML, and parsing might get completed even before the script download is finished and it starts its execution. So, in this case, it won’t block HTML parsing.

Independent Execution:

Once the async script is downloaded, the browser executes it as soon as possible, regardless of where the <script> tag is positioned in your HTML or whether other scripts have finished running.

This asynchronous execution allows the browser to continue rendering the page content while the script runs in the background.

if we’ve multiple script tags.

<script src="index.js" async></script>
<script src="main.js" async></script>
<script src="handler.js" async></script>

In above case, the file will be executed randomly based on the which file is downloaded quickly.

💡 Async does not guarantee the order of the execution of the script

Point to be noted

  • DOMContentLoaded and async scripts don’t wait for each other:
  • DOMContentLoaded may happen both before an async script ( if an async script finishes loading after the page is complete )
  • …or after an async script (if an async script is short or was in HTTP-cache)

Example: Index.html

Output:

Summary

  • Usually, The page content shows up immediately: async doesn’t block it.
  • DOMContentLoaded may happen both before and after async, no guarantees here.
  • Async scripts run in the “load-first” order. So whichever script loads first will run first. Here, ‘long.js’ runs first, probably because it might be cached. However, it may happen that ‘small.js’ will run first because it loads before ‘long.js’.

Async scripts are great when we integrate an independent third-party script into the page: counters, ads and so on, as they don’t depend on our scripts, and our scripts shouldn’t wait for them:

<!-- Google Analytics is usually added like this -->
<script async src="https://google-analytics.com/analytics.js"></script>

4️⃣ Defer

The defer attribute tells the browser not to wait for the script. Instead, the browser will continue to process the HTML, build DOM. The script loads “in the background”, and then runs when the DOM is fully built.

As a name suggest, defer means delaying or postponing some event. so here script execution will get delayed.

<!-- blocks DOMContentLoaded, but page might display early -->
<!-- & defer means that the DOM will be ready before run -->
<script src="index.js" defer></script>
Defer attribute

With defer attribute, HTML Parsing and fetching script from the network is done parallelly and once the HTML parsing is completed, script execution will start.

Important point to mention:

  • Defer is non blocking, in a sense that scripts with defer never block the rendering of the DOM.
  • Scripts with defer always execute when the DOM is ready (but before DOMContentLoaded event).

💡 Note: Unlike async, defer guarantees the order of the execution of the script and it will run in the order they’ve defined.

Example

Output:

  1. The page content shows up immediately.
  2. DOMContentLoaded event handler waits for the deferred script. It only triggers when the script is downloaded and executed.

Deferred scripts keep their relative order, just like regular scripts.

Let’s say, we have two deferred scripts: the long.js and then small.js:


// Long script
<script defer src="https://javascript.info/article/script-async-defer/long.js"></script>

// short script
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>

Browsers scan the page for scripts and download them in parallel, to improve performance. So in the example above both scripts download in parallel. The small.js probably finishes first.

…But the defer attribute, besides telling the browser “not to block”, ensures that the relative order is kept. So even though small.js loads first, it still waits and runs after long.js executes

That may be important for cases when we need to load a JavaScript library and then a script that depends on it.

The defer attribute is only for external scripts

💡 The defer attribute is ignored if the <script> tag has no src.

I’m not covering the dynamic script and lazy loading strategies in this blog post, as the post has already gotten slightly longer. However, if you want to read about them, here is the article you can check out:

Thank you for taking the time to read the post until the end. Your attention and interest are greatly appreciated.

Please 👏🏻 if you like this post. It will motivate me to continue creating high-quality content like this one.

Support Me

Thank you for taking the time to read my blog post! If you found it valuable, I would greatly appreciate it if you could share the post on Twitter and LinkedIn, etc. Your support in spreading the word about my content means a lot to me. Thank you again!

Follow me

I hope you found this post helpful. If you want to stay up-to-date with my latest work, be sure to follow me on Twitter, LinkedIn, and GitHub.

--

--

Hritik Jaiswal
helpshift-engineering

Software engineer at Helpshift, Maintainer at Robofied and TheAlgorithm, Open source contributor, Technical writer, Social ➡ https://linktr.ee/hritikdj