Basis Lab

Answers · The network and the web

Why does a <script> tag stop the rest of the page from being parsed?

In shortA script tag stops the parser because the script can change the page being built, so the browser must fetch and run it before it knows what comes next.

A script tag stops the parser because the parser cannot know what the next node should be until the script has run. A script can write into the document at the point where it sits, so the browser stops tokenising, fetches the file, parses it, executes it, and only then resumes.

The stop begins at the tag

The natural model, that the browser reads the whole document and then runs the scripts, is wrong in both order and mechanism. The parser is a tokeniser feeding a tree builder. The tokeniser has no memory of structure; the tree builder keeps an open element stack, pushing a node for every start tag and popping it for every end tag. An ordinary tag is safe to continue past because its effect on the tree is known from its name and attributes. When the tokeniser emits a script start tag, the tree builder cannot simply push a node and continue, because the script may call document.write and insert elements or text exactly where the script sits. The next token depends on the result of running the script, so the parser stops before the fetch even begins. It then waits for the network, parses the JavaScript, executes it, and returns to the byte stream. The tree stops growing for the whole of that wait.

980021000=0.467,280022=127.3,21000−9800=11200 bytes still to arrive\frac{9800}{21000}=0.467,\qquad \frac{2800}{22}=127.3,\qquad 21000-9800=11200\text{ bytes still to arrive}

The incremental nature is visible in the byte counts. The first 1400 bytes carry 9 nodes; the next 2800 bytes carry 22 more, so the tree grows by about 2800 / 22 = 127 bytes per node over that stretch. By 9800 bytes the browser has painted the header and the first paragraph, and 21000 - 9800 = 11200 bytes of the 21000 byte document are still in flight. The tree is already usable for the part received, so a script tag is not stopping an inevitable whole document parse. It is stopping a production line that had already produced visible output.

FIG 1Where the parser is stopped
parser stoppedresources fetching anyway0200400800120120 ms: parser meets the script140140 ms: scanner has found four more resources460460 ms: script arrives and runs620620 ms: first paintmilliseconds since the document started arriving
The parser is stopped from 120 ms to 460 ms. The scanner starts finding resources at 140 ms, so their fetches overlap the stall for 460 - 140 = 320 ms, but the parser itself does not restart until 460 ms.

The common assumption is that a script only matters when it runs, so its position in the document is a style choice. The timing says otherwise. The parser meets the tag at 120 ms, the script arrives and runs at 460 ms, so the parser is stopped for 460 - 120 = 340 ms. The fetch, not the execution, owns that interval, and the parser has handed control to the script before the rest of the document has been tokenised. During the same interval the read ahead scanner spots four more resources at 140 ms, which is 20 ms after the stop, and those fetches overlap the blocked parser for 460 - 140 = 320 ms. Without that overlap they would start when the parser resumes at 460 ms, so the 620 ms first paint would be pushed at least to 620 + 320 = 940 ms if the paint waits on them. The scanner hides the resource fetches but cannot restart the parser.

What removes the stop

A script with defer does not stop parsing while it is fetched: the browser starts the fetch and keeps building the tree, then runs the script after the document has finished, in document order. A script with async also starts fetching and keeps parsing, but runs the moment the file arrives, in no guaranteed order, and interrupts parsing when it does. The default halt is therefore not required by the language. It is required only when the script must run at its exact position, which is rare. Use defer when order matters and async when the script depends on nothing and nothing depends on it.

The rule has limits. An inline script has no file to fetch, but it still stops the parser at the tag while it executes, because the script may still write into the document. A script inserted by another script is invisible to the read ahead scanner, so its fetch starts later than a reference in the markup would. And async removes the fetch stall without removing every interruption: an async file that arrives mid parse stops the parser then. The halt is about where the script sits, not only about how long it takes to download.

Worth keeping

  • The parser halts at a plain script tag before the fetch, because the script can write into the document and change what the next token should be.
  • The expensive part of a blocking script is usually the network round trip, not the execution.
  • Defer removes the parser stall while preserving document order, and async removes it but runs whenever the file arrives.

Where this goes next

This question is one step of How a Browser Draws a Page, a course that works through its subject from the beginning. The page that teaches this part of it is From Bytes to a Tree, and it reads the whole way through whether or not you have an account.

What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.

Read that pageThe whole course

Other questions people ask about this

  1. Why is animating width so much slower than animating transform?Animating width is slower because each change forces layout and repaint, while a transform on a promoted element skips both and only reassembles the frame.
  2. Where should a session ID be stored so JavaScript can't read it?Store it in an HttpOnly cookie set by the server; that is the only browser storage JavaScript running on the page cannot read.
  3. What does it mean for an API method to be safe?A safe method is one that changes nothing on the server when it is called, so a caller may repeat it freely and a cache or a crawler may use it without anything being altered.
  4. Does signing out actually invalidate my token on the server?No. Signing out clears your browser's copy of the token, but the server keeps accepting that token until it expires.
  5. Why does the TLS handshake take so many round trips before any data is sent?It is not one key exchange. The handshake agrees a secret, proves the server's identity and checks for tampering, and modern TLS fits those into one round trip; a fresh connection still needs three before the page starts.
  6. If a website has HTTPS, can someone on the same Wi-Fi read my password?No. HTTPS encrypts the form contents and cookies, so someone on the same Wi-Fi cannot read the password itself; they can still see the site, timing and packet sizes.

Read alongside