Scripts in head
1. How the browser loads a page
When the browser receives an HTML document, it performs these steps:
- Parses the HTML line by line, top to bottom.
- When it hits a
<link>tag (CSS), it stops and loads the stylesheet, because styles affect rendering. - When it hits a
<script>tag, it stops parsing in order to run the JS.
Why? Because a script can change the DOM itself, or even remove elements that come later in the HTML (
document.write,innerHTML, and so on). So the browser has to wait for the JS to finish running before continuing to parse.
2. What happens if script is in head
<head>
<script src="main.js"></script>
</head>
<body>
<h1>Hello!</h1>
</body>Step by step:
- The browser starts reading the HTML.
- It reaches
<script src="main.js">. - It stops parsing the HTML.
- It waits for
main.jsto load over the network (can take hundreds of milliseconds). - It runs the script.
- Only then does it continue parsing the rest of the page.
During all that time:
- the DOM has not been built yet,
- the user sees no content,
- the FCP / LCP metrics suffer.
3. Problems this causes
| Problem | What happens |
|---|---|
| The HTML parser is blocked | The page does not build until the JS loads |
| A long "white page" | The user sees emptiness |
| Slow networks -> worse UX | Especially on 3G/4G |
| Worse Core Web Vitals | LCP, FCP, TTI, TBT all grow |
| Scripts get in the way of critical CSS | Critical styles do not apply in time |
4. How to avoid blocking
Way 1. defer
<script src="main.js" defer></script>What it does:
- The script loads in parallel with the HTML,
- but only runs after the DOM is fully parsed (before
DOMContentLoaded).
No blocking. Guaranteed execution order (in sequence). Ideal for most cases.
Way 2. async
<script src="analytics.js" async></script>What it does:
- The script loads asynchronously (in the background),
- and runs as soon as it loads, without waiting for others.
Used for independent scripts (analytics, ads, pixels). Execution order is not guaranteed.
Way 3. Moving scripts to the end of body
<body>
...
<script src="main.js"></script>
</body>By the time the JS loads:
- the HTML is fully parsed,
- the user already sees content,
- the JS runs as the last step.
This is the "old" but working way. In modern HTML, <script defer> has replaced it.
5. How to handle it in production
| Script type | Where and how to include it |
|---|---|
| Main logic (UI, SPA) | <script src="main.js" defer> |
| Analytics, ads | <script src="ga.js" async> |
| Inline initialization | <script> inside <head> (small initialization) |
| Legacy libraries | Better to move to the end of <body> |
6. Impact on metrics
| Metric | What gets worse with scripts in head |
|---|---|
| FCP (First Contentful Paint) | The first content shows up later |
| LCP (Largest Contentful Paint) | The main element renders later |
| TTI (Time to Interactive) | JS blocks the main thread |
| TBT (Total Blocking Time) | Grows because of heavy synchronous scripts |
7. Illustration (in words)
HTML: <head> -> <script> -> <body>
↓ loading
[======= JS is loading =======]
↓ execution
[======= JS is running =======]
↓ only then is the DOM builtWith defer, loading happens in parallel, and execution happens at the end:
HTML and JS load together -> DOM built -> JS runsShort summary
| Scenario | What it does | Consequence |
|---|---|---|
<script> in head with no attributes | Blocks parsing | Slow |
<script defer> | Loads in parallel, runs after the DOM | Optimal |
<script async> | Loads and runs independently | Only for independent scripts |
<script> at the bottom of body | Waits until the end of the HTML | No blocking |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.