Marcell Ciszek Druzynski
← Back to blog posts

Async vs Defer: Optimizing JavaScript Loading

When adding JavaScript to your web pages, how you load scripts can significantly impact performance and user experience. Traditionally, developers were advised to place <script> tags at the bottom of the <body> element because script parsing blocks HTML rendering. Today, we have better options with the async and defer attributes.

This guide explains how these attributes work and helps you choose the right approach for different scenarios.

Default Script Loading Behavior

By default, when the browser encounters a <script> tag without any special attributes:

  1. HTML parsing pauses
  2. The script is downloaded
  3. The script executes
  4. HTML parsing resumes

This behavior creates a significant performance issue, especially when scripts are placed in the <head>:

script in head without any attribute

As shown above, the browser must halt HTML parsing while handling the JavaScript file. This interruption can lead to a poor user experience, with the page appearing to freeze or load slowly. Users might see a blank or partially rendered page until all blocking scripts are processed.

This is why the traditional recommendation was to place scripts at the bottom of the <body> tag - allowing HTML and CSS to download and render first before any JavaScript executes.

The Async Attribute

Adding the async attribute to a script tag changes the loading behavior significantly:

1<script async src="analytics.js"></script>
1<script async src="analytics.js"></script>

With the async attribute:

  1. HTML parsing continues uninterrupted while the script downloads in parallel
  2. Once downloaded, the script executes immediately (which temporarily pauses HTML parsing)
  3. HTML parsing resumes after script execution

using async script

Key Characteristics of Async Scripts:

When to Use Async:

✅ For scripts that function independently and don't rely on other scripts
✅ When the script doesn't need to manipulate DOM elements during initial load
✅ For third-party scripts where execution timing isn't critical (analytics, ads, etc.)
✅ When prioritizing page loading speed above script execution order

When to Avoid Async:

❌ When the script depends on other scripts that may not have loaded yet
❌ If the script needs to access or modify DOM elements that may not exist when it executes
❌ When script execution order is critical
❌ For core functionality scripts that the page relies on to function properly

The Defer Attribute

The defer attribute offers a more predictable loading pattern:

1<script defer src="main.js"></script>
1<script defer src="main.js"></script>

With the defer attribute:

  1. HTML parsing continues uninterrupted while the script downloads in parallel
  2. The script only executes after HTML parsing is complete, but before the DOMContentLoaded event
  3. Multiple deferred scripts execute in the order they appear in the document

using defer script

Key Characteristics of Defer Scripts:

When to Use Defer:

✅ For scripts that need access to a fully parsed DOM
✅ When script execution order matters
✅ For most application logic that isn't needed during initial rendering
✅ When you want to maintain the benefits of placing scripts in the <head> without blocking rendering

When to Avoid Defer:

❌ When the script needs to execute as early as possible
❌ For critical resources that should load with high priority
❌ When you need to support very old browsers (though modern browser support is excellent)

Practical Examples

Third-Party Analytics

Analytics scripts are perfect candidates for async since they typically operate independently:

1<script async src="https://analytics-provider.com/tracking.js"></script>
1<script async src="https://analytics-provider.com/tracking.js"></script>

Application Core Framework

For frameworks and libraries your application depends on, defer is often more appropriate:

1<script defer src="https://cdn.example.com/framework.js"></script>
2<script defer src="/js/app.js"></script>
1<script defer src="https://cdn.example.com/framework.js"></script>
2<script defer src="/js/app.js"></script>

This ensures your framework loads before your application code, and both execute after the DOM is ready.

Critical Rendering Scripts

Some scripts might be essential for initial rendering. For these, inline critical code in the <head> and defer the rest:

1<head>
2 <script>
3 // Critical inline code for initial render
4 document.documentElement.classList.remove("no-js");
5 </script>
6 <script defer src="/js/main.js"></script>
7</head>
1<head>
2 <script>
3 // Critical inline code for initial render
4 document.documentElement.classList.remove("no-js");
5 </script>
6 <script defer src="/js/main.js"></script>
7</head>

Module Scripts

Modern JavaScript modules (type="module") behave like defer by default:

1<script type="module" src="app.module.js"></script>
1<script type="module" src="app.module.js"></script>

This script automatically defers execution until after HTML parsing, even without the defer attribute. However, you can still add async to a module script if needed:

1<script type="module" async src="standalone-module.js"></script>
1<script type="module" async src="standalone-module.js"></script>

Browser Support

Both async and defer attributes are well-supported in all modern browsers. If you need to support very old browsers (IE9 and below), consider using a fallback loading strategy.

Summary: Choosing Between Async and Defer

Here's a quick decision guide for choosing which attribute to use:

| Scenario | Recommended Attribute | | ---------------------------------------------------------- | --------------------- | | Script operates independently | async | | Script requires access to fully parsed DOM | defer | | Script execution order matters | defer | | Third-party scripts (analytics, ads) | async | | Application core functionality | defer | | Script loads dynamic content not needed for initial render | defer |

The best practice today is to place scripts in the <head> with either async or defer attributes rather than at the end of the <body>. This allows the browser to discover and start downloading scripts earlier while preventing render blocking.

For most application scripts, defer is the safer choice as it ensures DOM availability and maintains execution order. Use async when script independence and earliest possible execution are more important than execution order.

By strategically choosing between async and defer, you can significantly improve your site's performance and user experience.