Every week I analyze a handful of websites for rendering performance. The main culprit blocking rendering is JavaScript execution. Frequently, I notice this blocking JavaScript is loaded with the ASYNC tag. Wha?! Shouldn’t ASYNC not block rendering? Sadly, no. Not only can ASYNC scripts block rendering, they can also block synchronous scripts from executing. Presumably, scripts that are made synchronous are important for the critical content on the page, so delaying their execution causes further harm to the user experience.

In this article, I make the argument that DEFER should be the default choice over ASYNC. To set the stage, I’m going to review how browsers evolved to make scripts faster with preloaders and the motivation for the ASYNC and DEFER attributes. If you know all of that stuff, then skip to the ASYNC and DEFER section.

A few conventions:

  • “script” typically means an external JavaScript file that has to be downloaded
  • “synchronous script” means a script done without ASYNC and DEFER which thus blocks HTML parsing and rendering

Parallelism is important for performance

I believe the single biggest improvement in the world of web performance was when browsers started downloading scripts in parallel. Before 2006, all browsers downloaded and executed external scripts sequentially. In those old days of sequential script downloading, this markup:

<script src="aphid.js"></script>
<script src="bmovie.js"></script>
<script src="seaserpent.js"></script>
<img src="deejay.gif">
<img src="elope.gif">

would result in this waterfall chart:

aphid.js        ====xxx
bmovie.js              =====xx
seaserpent.js                 =====xx
deejay.gif                           =====
elope.gif                            =====
DOM Interactive                      *
image render                              *
Waterfall 1: pre-2006 sequential script loading

In this handcrafted waterfall chart I’m using “==” to show download time and “xx” to indicate script parse and execution. (I make aphid.js faster to download and slower to execute for reasons that will become clear in a minute.) Notice how after each script is downloaded, there’s a little bit of time to parse and execute the script. DOM Interactive (performance.timing.domInteractive) fires after the browser finishes parsing the HTML document (which includes the time to parse & execute all synchronous scripts). The image render time is an approximation that doesn’t include image decoding and assumes there are no other blocking resources like stylesheets.

There’s a lot of inefficiencies in this old approach. While it’s true that scripts need to be executed sequentially, they can be downloaded in parallel. And scripts definitely don’t need to block other non-script resources, like images.

Starting with IE8 and followed soon after by other browsers, the concept of a preloader (or lookahead parser or speculative parser) was introduced where scripts could be downloaded in parallel. With the implementation of preloaders, pages got INCREDIBLY faster. Here’s the improved waterfall for our example:

aphid.js        ====xxx
bmovie.js       =====  xx
seaserpent.js   =====    xx
deejay.gif      =====
elope.gif       =====
DOM Interactive            *
image render               *
Waterfall 2: benefits of the preloader

In this waterfall that benefits from the browser preloader, we see that all the resources are downloaded in parallel. (In some browsers, the images might be given a lower priority and start downloading a bit later.) DOM Interactive still has to wait for all three scripts to parse and execute, but that happens much earlier which means the images are shown more quickly resulting in a happier user experience.

Synchronous scripts block HTML parsing

Preloaders improve web performance by changing it so synchronous scripts don’t block other resources from downloading. But synchronous scripts still block the browser’s HTML parser: when the HTML parser reaches a SCRIPT tag it stops until that script has been downloaded (if it’s an external script), parsed, and executed. When the HTML parser is blocked it means the user has to wait to see the content on the page. That’s why deejay.gif and elope.gif in the previous waterfall are blocked from rendering until all the scripts are done parsing and executing.

To avoid synchronous scripts blocking the HTML parser, developers started figuring out ways to load scripts asynchronously. Not ALL scripts should be loaded asynchronously. If the script is needed to render the critical content on the page, then it should be loaded synchronously. But if the script isn’t needed for critical rendering, then it can be loaded asynchronously. In the old days (2009), we had hacks for loading scripts asynchronously. Then the ASYNC and DEFER attributes were added to the HTML spec.

ASYNC and DEFER

ASYNC and DEFER are similar in that they allow scripts to load without blocking the HTML parser which means users see page content more quickly. But they do have differences:

  • Scripts loaded with ASYNC are parsed and executed immediately when the resource is done downloading. Whereas DEFER scripts don’t execute until the HTML document is done being parsed (AKA, DOM Interactive or performance.timing.domInteractive).
  • ASYNC scripts may load out-of-order, whereas DEFER scripts are executed in the order in which they appear in markup. (Although there’s a bug that makes DEFER’s execution order questionable in IE<=9.)

Even though ASYNC and DEFER don’t block the HTML parser, they can block rendering. This happens when they’re parsed and executed before rendering is complete and take over the browser main thread. There’s nothing in the spec that says they have to wait until rendering is complete. ASYNC scripts execute immediately once they finish downloading, and DEFER scripts execute after DOM Interactive.

To illustrate ASYNC’s behavior, let’s add ASYNC to aphid.js from our earlier example:

<script ASYNC src="aphid.js"></script>
<script src="bmovie.js></script>
<script src="seaserpent.js"></script>
<img src="deejay.gif">
<img src="elope.gif">

Here’s what the ASYNC waterfall looks like:

aphid.js        ====xxx
bmovie.js       =====  xx
seaserpent.js   =====    xx
deejay.gif      =====
elope.gif       =====
DOM Interactive            *
image render               *
Waterfall 3: aphid.js ASYNC

Even though aphid.js is ASYNC, it blocks the other scripts from executing because it finished downloading first. In other words, an ASYNC script blocks all synchronous scripts that finish downloading after it (Cuzillion test).

To see how DEFER works, let’s add it to the example markup:

<script DEFER src="aphid.js"></script>
<script src="bmovie.js></script>
<script src="seaserpent.js"></script>
<img src="deejay.gif">
<img src="elope.gif">

Here’s what the DEFER waterfall looks like:

aphid.js        ====     xxx
bmovie.js       =====xx
seaserpent.js   =====  xx
deejay.gif      =====
elope.gif       =====
DOM Interactive          *
image render             *
Waterfall 4: aphid.js DEFER

Because DEFER’ed scripts are executed after DOM Interactive, the synchronous scripts in the page (bmovie.js and seaserpent.js) are able to execute first even though they finish downloading later. Comparing the ASYNC and DEFER waterfalls, we see that using DEFER makes DOM Interactive fire sooner and allows rendering to proceed more quickly.

Why prefer DEFER

The waterfalls above illustrate how ASYNC and DEFER cause JavaScript execution to happen at different times in your page. DEFER always causes script execution to happen at the same time as or later than ASYNC. Presumably, scripts are made DEFER or ASYNC because they are less important for the critical content on the page. Therefore, it’s better to use DEFER so that their execution happens outside of the main rendering time.

DEFER scripts can never block synchronous scripts, while ASYNC scripts might depending on how quickly they download. Synchronous scripts are typically made synchronous because they are important for the critical content on the page. Therefore, it’s better to use DEFER so that synchronous scripts are not blocked from executing and their critical work is completed more quickly.

My waterfalls above are hypothetical, but it’s not too hard to find occurrances of ASYNC’s blocking behavior in the wild. Instagram has a small async script on their front page. In this WebPageTest result for Instagram that ASYNC script is request 4. Looking at the Timeline we see that the ASYNC script is executed before all the synchronous scripts around 0.6s into the page, way before DOM Interactive at 1.2s.

To see the benefits of DEFER on a live site we can look at Yelp. Their WebPageTest result includes a DEFER script as the second request. This script finishes downloading at 0.6s but doesn’t execute until DOM Interactive at 1.5s, thus allowing the other synchronous scripts in the page to be executed first and get rendering to happen sooner.

What it comes down to is the value of delaying DEFER script execution until DOM Interactive versus risking ASYNC scripts executing sooner if they download quickly. Thus, it’s important to know your DOM Interactive time. For the Alexa Top 100, the median DOM Interactive time is 2.1 seconds and the 95th percentile is 11.2 seconds (based on HTTP Archive data from Nov 15 2016). That value is large enough to create a window where ASYNC scripts could finish downloading before DOM Interactive and end up causing a further delay to page rendering.

If you use ASYNC on any of your scripts, do a test to see if DEFER makes rendering happen sooner. If you have a high DOM Interactive time, you might be pleasantly surprised at the results.

ABOUT THE AUTHOR

Steve works at SpeedCurve on the interplay between performance and design. He previously served as Google's Head Performance Engineer, Chief Performance Yahoo!, and Chief Performance Officer at Fastly. Steve has pioneered much of the work in the world of web performance. He is the author of High Performance Web Sites and Even Faster Web Sites. He is the creator of many performance tools and services including YSlow, the HTTP Archive, Episodes, ControlJS, and Browserscope. Steve taught CS193H: High Performance Web Sites at Stanford and serves as co-chair of Velocity, the web performance and operations conference from O'Reilly.

15 Responses to “Prefer DEFER Over ASYNC”

  1. Senthil

    Nice write up, thank you. Did some testing and it looks like “defer” attribute does block the “DOMContentLoaded” event e.g. http://stevesouders.com/cuzillion/?c0=hj1hfft4_5_f&c1=hj2hfff1_1_f&c2=hj2hfff1_1_f&t=1481566314349 , whereas “async” attribute doesn’t block e.g. http://stevesouders.com/cuzillion/?c0=hj1hfff4_5_t&c1=hj2hfff1_1_f&c2=hj2hfff1_1_f&t=1481567044196 This may be a critical factor as lot of application bind initialization events to “DOMContentLoaded”.

  2. Dany Gielow

    What about inline script tags that insert other ASYNC scripts with the createElement-insertBefore method as described in 2010 on your blog?
    This is probably how the majority of third party scripts (analytics, ads) are included.
    Should they also be inserted as DEFER as they could finish downloading before other scripts?

  3. Aziz Khambati

    Hi Steve,
    Great writeup. I wanted to share our experience and what we do with housing.com.
    We have a script tag which inserts more script tags with the async=false attribute.
    async=false tells the browser to load the scripts async but in order. That gives us the advantage of splitting our js bundles for better caching strategy.
    We did not like defer as it delays execution till DOM Interactive.

    And for browser preloaders we use link rel=”preload” as=”script”
    The link tags tells the browser to load the scripts but they are loaded with low priority and when the script is executed, the priority is changed to “high”. We don’t load any unnecessary scripts till our base scripts are loaded so we don’t have to worry much about priority.

    My colleague Rahul gave a talk about it at JSFoo 2016 and the slides are available here: https://speakerdeck.com/dxtr026/building-fast-and-performant-apps?slide=19

  4. Prefer `defer` Over `async` | SeoRocketBar Network

    […] Direct Link to Article — Permalink […]

  5. Prefer `defer` Over `async` - A Geek's View

    […] Direct Link to Article — Permalink […]

  6. Eliazer Braun

    Steve, Thanks for the article.

    How about using both async and defer? is it something wrong with that?

  7. Flimm

    Where is the spec that says that defer scripts are executed in order? I see the assumption made everywhere, but I see no proof in any spec or standards document that it can be made safely.

  8. Dave

    “a, b, sea, d, e”? What hidden magic lurks here?!
    Seriously, that is a fantastic write-up, and all made crystal clear. Thank you!

  9. Steve Souders

    Senthil: Yes, the spec says that DEFER scripts run after domInteractive but before domContentLoaded.

    Dany: In most websites, by the time these dynamically-loaded ASYNC scripts get executed the main rendering is probably done, so I don’t think it would be a big impact if they were converted to DEFER. Also, I didn’t mention it in the article but some ASYNC scripts should probably not be converted to DEFER, such as analytics and perhaps ads. The main problem I see in the websites I review is the website is loading its own (large) scripts that are not critical for rendering as ASYNC, and when these (large) scripts get executed they block the main thread.

    Aziz: Great techniques! Thanks for sharing. If the scripts are rendering critical content in the viewport I agree they should not be delayed. Otherwise, using DEFER would be better.

    Eliazer: ASYNC overrides DEFER, so in most browsers specifying both is the same as just saying ASYNC. Since we want DEFER to take precedence, we don’t want to specify both.

    Flimm: DEFER scripts are added to a queue that is processed in a FIFO manner. See step #15 here

    Dave: You’re the only one who commented on the script names! That’s based on a Far Side cartoon that I read 30 years ago.

  10. Bailey

    Hi Steve,

    Love love love your article!
    Does it help with the speed index using DEFER over ASYNC?

  11. Front-End Performance Checklist 2017 (PDF, Apple Pages) -

    […] practice, it turns out we should prefer defer to async49 (at a cost to users of Internet Explorer50 up to and including version 9, because you’re likely […]

  12. WebPerformance notes from PerfPlanet – Technology 2.0

    […] Provide primary content 3. Allow interation 4. Show secondary content 5. Below the fold Day 12: Prefer DEFER over ASYNC Async will not block HTML parsing but it will block rendering. So prefer DEFER over ASYNC when […]

  13. Front-End Performance Checklist 2017 – Smashing Magazine – VaughnPaul.com

    […] this for scripts is with the defer and async attributes in HTML.In practice, it turns out we should prefer defer to async51 (at a cost to users of Internet Explorer52 up to and including version 9, because you’re likely […]

  14. Front-End Performance Checklist 2017 (PDF, Apple Pages) – Web Guy Help

    […] practice, it turns out we should prefer defer to async51 (at a cost to users of Internet Explorer52 up to and including version 9, because you’re likely […]

  15. Web Development Reading List #163: The End-Of-Year Wrap-Up – Web Guy Help

    […] Souders explains why setting async on a script can cause various issues and how defer is different from that14. His conclusion: defer should be your default choice over […]