---
title: 'Resource Loading Order'
source: 'https://academia.sh/en/courses/web-fundamentals-and-html/resource-loading-order'
course: 'Web Fundamentals and HTML'
language: en
updated: '2026-08-17T18:09:32+00:00'
license: 'CC BY-SA 4.0'
---

# Resource Loading Order

How script and style files hold up parsing and paint; comparing defer and async loading modes through an event timeline.

The previous lesson assumed the tree is built from the token stream without interruption.
Real documents break this assumption: while parsing is under way, the document refers to
resources beyond itself, and some of these references halt parsing.

The What the Browser Does lesson in the How the Internet Works course showed that these
subresource requests exist: when an address was typed, the server saw more than one request.
This lesson takes up **when** those requests are made and how they affect the document being
processed. This is not a performance detail, it is a constraint that decides how the document
gets written.

## A Reference Is Born During Parsing

The browser reads the document from start to finish. The moment it sees a `link` element it
sends the style file's request, the moment it sees an `img` element it sends the image's
request. It does not wait for the end of the document. This has a direct consequence: a
resource's **place** in the document decides the **moment** it is requested. A style file
written at the end of the document is requested later than one written at the start.

Resources do not have the same effect on parsing. There are three behaviors.

| Resource | Halts parsing | Holds up paint |
|---|---|---|
| Image | No | No |
| Style file (`link rel="stylesheet"`) | No | Yes |
| Script (no attribute written) | Yes | Indirectly |
| Script (`defer`) | No | No |
| Script (`async`) | While executing | Indirectly |

## Why a Script Halts Parsing

When a script element with no attribute written is seen, parsing halts, the script is
downloaded, executed, and only then does parsing continue. This behavior is not a shortcoming,
it is a necessity: the script can read the tree built up to that moment and add nodes to it.
If the parser ran at the same time, the tree the script sees and the tree the parser is
building would collide.

The price of this necessity is that the rest of the document is not processed while the
script downloads. There are two ways to avoid paying that price, and both give a promise
about when the script will reach the tree.

**Defer (`defer`)**: the script downloads without halting parsing, and executes once parsing
has finished. Multiple deferred scripts execute in the order they are written in the
document. This is the only safe mode for scripts that depend on one another.

**Async (`async`)**: the script downloads without halting parsing, and executes the moment it
finishes downloading; parsing halts while it executes. Execution order depends on download
order, not the document's order. It is for scripts that run on their own, with no dependency
on other scripts.

This distinction is not a preference, it is a contract: writing `async` means "my order does
not matter." If this attribute is written on a script whose order does matter, the bug
surfaces only under certain conditions and is hard to reproduce.

## Why a Style File Holds Up Paint

A style file does not halt parsing — tree building continues. But paint cannot happen until
style computation is complete; if it did, the page would first appear unstyled, then jump to
its styled appearance. The browser waits instead.

There is a second, less well-known rule too: **a script does not execute while a style file
is pending.** The reasoning is the same kind. A script can ask for an element's computed style
value; if there is a rule that has not been applied yet, the answer it gets would be wrong.
For this reason a script is held back until the style file arrives, even if the script itself
has already finished downloading.

## Event Timeline

The script below runs these rules as an event model. It is not a real browser, it is a model
of the rules: every piece of the document holds a unit of parsing time, every resource's fetch
time is given.

```js
// loading.mjs — event model of the parsing/loading rules
const document_ = [
  { kind: "markup", name: "head start", duration: 1 },
  { kind: "resource", name: "style.css", format: "style", fetch: 6 },
  { kind: "markup", name: "body first half", duration: 2 },
  { kind: "resource", name: "measurement.js", format: "script", mode: "blocking", fetch: 3, exec: 1 },
  { kind: "markup", name: "body second half", duration: 2 },
  { kind: "resource", name: "chart.js", format: "script", mode: "deferred", fetch: 5, exec: 1 },
  { kind: "markup", name: "body end", duration: 1 },
];

const log = [];
let clock = 0;
const deferred = [];
let styleReady = Infinity;

for (const part of document_) {
  if (part.kind === "markup") {
    log.push([clock, clock + part.duration, "parsing: " + part.name]);
    clock += part.duration;
    continue;
  }
  if (part.format === "style") {
    log.push([clock, clock, "request: " + part.name + " (in background)"]);
    styleReady = clock + part.fetch;
    continue;
  }
  if (part.mode === "blocking") {
    const fetchEnd = clock + part.fetch;
    const execStart = Math.max(fetchEnd, styleReady);
    log.push([clock, fetchEnd, "HALTED: fetching " + part.name]);
    if (execStart > fetchEnd) {
      log.push([fetchEnd, execStart, "HALTED: pending style"]);
    }
    log.push([execStart, execStart + part.exec, "exec: " + part.name]);
    clock = execStart + part.exec;
    continue;
  }
  if (part.mode === "deferred") {
    log.push([clock, clock, "request: " + part.name + " (in background)"]);
    deferred.push({ ...part, ready: clock + part.fetch });
  }
}

const parsingEnd = clock;
log.push([clock, clock, "parsing complete"]);
for (const script of deferred) {
  const start = Math.max(clock, script.ready);
  log.push([start, start + script.exec, "exec (deferred): " + script.name]);
  clock = start + script.exec;
}

for (const [start, end, name] of log) {
  console.log(String(start).padStart(3) + " - " + String(end).padStart(3) + "  " + name);
}
console.log("parsing end:", parsingEnd, "| all scripts done:", clock);
```

```
  0 -   1  parsing: head start
  1 -   1  request: style.css (in background)
  1 -   3  parsing: body first half
  3 -   6  HALTED: fetching measurement.js
  6 -   7  HALTED: pending style
  7 -   8  exec: measurement.js
  8 -  10  parsing: body second half
 10 -  10  request: chart.js (in background)
 10 -  11  parsing: body end
 11 -  11  parsing complete
 15 -  16  exec (deferred): chart.js
parsing end: 11 | all scripts done: 16
```

The timeline makes three things visible. First, `measurement.js` halted parsing for three
units (the 3–6 span) and, even after it finished downloading, waited one more unit; what it
was waiting for was `style.css` being ready. Second, because `chart.js` was deferred, it
never halted parsing at all, its request went out at unit 10, and it executed only after
parsing finished. Third, when parsing finishes (11) and when the scripts finish (16) are
separate moments; when the document becomes readable, the whole behavior layer may not yet be
ready.

The numbers are this model's assumptions; real durations depend on network conditions and
file sizes. What does not change is the ordering.

## Placement Decisions

The rules lead to a few direct consequences.

A style file is written in the document's head section. If it is requested late, paint starts
late; if it is written in the middle of the body, content that has been painted up to that
point gets restyled afterward.

A script mode that does not halt parsing is preferred over a script with no attribute
written. Writing the script at the end of the document also saves most of parsing, but it
does not start the download as early as defer does: a script written at the end only has its
request go out once the parser reaches that point.

The browser does not sit idle while the parser is halted. A mechanism that scans ahead
through the rest of the document, only to look for resource references, sends the requests it
finds early. This behavior relies on resources being explicitly declared in the document: a
resource whose address a script computes and produces is not visible to this scan.

## Summary

- Resource requests are born during parsing; a resource's place in the document decides the
  moment it is requested.
- A script with no attribute written halts parsing, because the script can read and change the
  tree built up to that moment.
- Defer executes the script after parsing finishes and in the document's order; async mode
  executes it the moment it finishes downloading and gives no ordering guarantee.
- A style file does not halt parsing but holds up paint; a script also does not execute while
  a style file is pending.
- When parsing finishes and when the scripts finish are separate moments; the document being
  readable does not mean the whole behavior layer is ready.

## Next Step

This topic established how the document is processed: bytes turning into a tree, the tree
turning into boxes, resources' effect on this chain. The next topic begins writing the
document itself. The first question is every document's outermost structure: what the
document type declaration is for, what separates the head and body sections, and what the
parser builds when none of these is written at all.
