Script Loading: async vs defer - When to Use Each

Easyβ€’

Regular Script

Classic parser-blocking behavior

  • - Blocks HTML parsing
  • - Predictable immediate execution

async

Independent script execution

  • - Downloads in parallel
  • - Executes when ready (order not guaranteed)

defer

Best default for app bundles

  • - Downloads in parallel
  • - Executes after parse, in order

type=module

Module graph semantics

  • - Deferred by default
  • - Use async only for independent module timing

Core Lens

Choose by parser blocking risk, execution-order guarantees, and DOM-readiness requirements.

Flow

Parser sees script→
Download→
Execute timing→
DOM ready behavior

Script loading choices are about parser blocking, execution order, and DOM-readiness guarantees. Interview-safe explanations cover regular vs async vs defer; deeper browser-aware explanations include parser interruptions, DOMContentLoaded timing, and module-script behavior.

Asked In

Quick Decision Guide

Use defer for most application scripts that need predictable order or depend on the parsed DOM.

Use async for independent scripts like analytics, ads, or widgets where execution order does not matter.

Use a plain external <script> without attributes only when parser-blocking behavior is truly required.

type="module" scripts are deferred by default, so they usually behave more like defer than classic blocking scripts.

Interview-safe vs Deeper Browser Model

Interview-safe model

Use this in interviews:

β€’plain <script src> blocks parsing
β€’async downloads in parallel and executes as soon as ready
β€’defer downloads in parallel and executes after parsing, in document order
β€’type="module" behaves like deferred by default

Deeper browser model

For senior-level answers, add:

β€’parser blocking is about execution time, not just download time
β€’async scripts can still interrupt parsing when they execute
β€’deferred classic scripts run before DOMContentLoaded
β€’module scripts are deferred by default, and defer has no extra effect on them

Practical framing

Ask two questions:

1. Do I need deterministic execution order?

2. Must this run before the DOM is parsed?

If order or parsed DOM matters, default to defer. If fully independent, use async.

Regular Script Loading (No Attribute)

<script src="script.js"></script>

How It Works

For a classic external script without async or defer:

1. HTML parsing stops

2. The browser fetches the script

3. The script executes immediately

4. HTML parsing resumes

Mental Model

> The parser hits the script tag and must wait.

Why It Matters

Parser-blocking scripts delay visible progress because the browser cannot continue parsing the document until that script is ready and executed.

When It Still Makes Sense

β€’tiny critical inline bootstrap code
β€’code that must run before later markup is parsed
β€’rare compatibility or sequencing cases where blocking is intentional

Example

<head>
  <script>
    window.APP_CONFIG = { apiBase: '/api' };
  </script>
</head>
<body>
  <script src="app.js"></script>
</body>

Trade-off

Blocking is simple and predictable, but usually the worst option for page startup performance.

The async Attribute

The `async` Attribute

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

How It Works

For a classic external script with async:

1. HTML parsing continues

2. The script downloads in parallel

3. As soon as the script is ready, it executes immediately

4. That execution may happen before parsing completes

Mental Model

> Download early, run whenever ready.

Key Characteristics

Advantages

β€’non-blocking download
β€’fast execution once the resource is ready
β€’good for scripts that do not depend on document order or the parsed DOM

Disadvantages

β€’execution order is not guaranteed between multiple async scripts
β€’can run before the DOM is fully parsed
β€’can interrupt parsing when it executes

Best Use Cases

β€’analytics
β€’ads
β€’third-party tags
β€’independent widgets
β€’scripts that do not depend on other scripts

Common Mistake

<!-- ❌ Order is not guaranteed -->
<script async src="jquery.js"></script>
<script async src="plugin.js"></script>

If plugin.js expects jQuery, this can break because plugin.js may execute first.

Better Alternative

<script defer src="jquery.js"></script>
<script defer src="plugin.js"></script>

Interview Takeaway

Use async when independence matters more than ordering.

The defer Attribute

The `defer` Attribute

<script defer src="app.js"></script>

How It Works

For a classic external script with defer:

1. HTML parsing continues

2. The script downloads in parallel

3. Execution waits until parsing finishes

4. Multiple deferred classic scripts execute in document order

Mental Model

> Download now, execute later, preserve order.

Key Characteristics

Advantages

β€’non-blocking download
β€’runs after parsing completes
β€’preserves execution order across deferred classic scripts
β€’avoids parser interruption during download phase
β€’strong default for application code

Disadvantages

β€’does not run as early as async
β€’not useful when immediate execution is required

Why It’s Often the Best Default

Application scripts usually need at least one of these:

β€’parsed DOM
β€’predictable order
β€’stable startup sequence

That makes defer a better default than async for most app bundles.

Example

<script defer src="runtime.js"></script>
<script defer src="vendor.js"></script>
<script defer src="app.js"></script>

These execute after parsing, in the same order they appear.

Important Detail

Deferred classic scripts run before DOMContentLoaded, which means that event waits for them to finish.

Execution Order and DOM Readiness

This is where interview answers usually get separated.

πŸ”₯ Insight

The real choice is often not β€œfast vs slow.” It is unpredictable early execution vs predictable late execution.

Comparison

Regular script

β€’blocks parsing
β€’executes immediately
β€’order follows document order

`async`

β€’does not block download
β€’executes as soon as ready
β€’order is not guaranteed
β€’may run before DOM is fully parsed

`defer`

β€’does not block download
β€’executes after parsing
β€’order is preserved
β€’good fit for DOM-dependent scripts

Rule of Thumb

β€’Need predictable order? β†’ defer
β€’Need DOM to be parsed first? β†’ defer
β€’Script is fully independent? β†’ async
β€’Need immediate parser-blocking behavior? β†’ plain script

Module Scripts

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

Module scripts are deferred by default.

What That Means

β€’they download in parallel with parsing
β€’they execute after parsing by default
β€’defer has no effect on them

Important Nuance

You can still mark a module script as async:

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

In that case, the module and its dependency graph are fetched in parallel and evaluated as soon as available.

Practical Takeaway

If you are using modern ES modules, you usually do not need to add defer manually.

Performance and Common Pitfalls

What Actually Improves Performance

β€’avoid parser-blocking scripts on the critical path
β€’use defer for main application bundles
β€’use async for truly independent third-party scripts
β€’reduce script size and count where possible

Common Pitfalls

1. Using `async` for dependent scripts

<script async src="vendor.js"></script>
<script async src="app.js"></script>

If app.js depends on vendor.js, this is fragile.

2. Blocking parsing with non-critical third-party code

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

This slows parsing for no good reason.

3. Assuming `async` waits for DOM

n

It does not. An async script can execute before the DOM is fully parsed.

4. Adding `defer` to module scripts expecting different behavior

Module scripts already defer by default.

Best Practices

Decision Tree

Does the script need predictable order or parsed DOM?
β”œβ”€ Yes β†’ use defer
└─ No β†’ is it independent?
   β”œβ”€ Yes β†’ use async
   └─ No β†’ use defer

Common Patterns

Pattern 1: Modern Web App

<body>
  <div id="root"></div>

  <script defer src="runtime.js"></script>
  <script defer src="vendor.js"></script>
  <script defer src="app.js"></script>
</body>

Pattern 2: App + Analytics

<body>
  <script async src="analytics.js"></script>
  <script defer src="app.js"></script>
</body>

Pattern 3: ES Modules

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

Final Rule of Thumb

For most application code, defer is the safest default.

Use async only when the script is independent and order does not matter.