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:
- HTML parsing pauses
- The script is downloaded
- The script executes
- HTML parsing resumes
This behavior creates a significant performance issue, especially when scripts are placed in the <head>
:
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:
With the async
attribute:
- HTML parsing continues uninterrupted while the script downloads in parallel
- Once downloaded, the script executes immediately (which temporarily pauses HTML parsing)
- HTML parsing resumes after script execution
Key Characteristics of Async Scripts:
- Non-blocking downloads: The script downloads without blocking HTML parsing
- Unpredictable execution timing: Executes as soon as it's downloaded, regardless of HTML parsing status
- No guaranteed order: Multiple async scripts execute in the order they finish downloading, not in the order they appear in the HTML
- DOMContentLoaded independence: May execute before or after the DOMContentLoaded event
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:
With the defer
attribute:
- HTML parsing continues uninterrupted while the script downloads in parallel
- The script only executes after HTML parsing is complete, but before the DOMContentLoaded event
- Multiple deferred scripts execute in the order they appear in the document
Key Characteristics of Defer Scripts:
- Non-blocking downloads: Like async, defer downloads scripts without blocking HTML parsing
- Predictable execution timing: Always executes after HTML is fully parsed
- Preserved execution order: Multiple deferred scripts execute in document order
- Pre-DOMContentLoaded execution: Always executes before the DOMContentLoaded event fires
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:
Application Core Framework
For frameworks and libraries your application depends on, defer
is often more appropriate:
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:
Module Scripts
Modern JavaScript modules (type="module"
) behave like defer
by default:
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:
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.