skip to content

On a web page, when is putting JavaScript directly in an inline <script> block the right performance choice instead of linking a separate .js file, and what does inlining cost you?

level: middleimportance: should knowfreq 42%

answer

  1. round trip versus cache reuse
  2. bytes ship with every HTML response
  3. keep it in the hundreds of bytes
  4. CSP needs a nonce or hash

basics

~20 s

Inline only code that must run before the first paint and is small enough that a separate request would cost more than the bytes — a few hundred bytes at most. Inlined code cannot be cached separately and ships with every HTML response.

solid answer

~50 s

Inlining buys you one thing: the code is already there when the parser reaches it, with no connection, no request and no round trip. That is worth it for a tiny piece of code whose result must exist before anything is painted — a theme class that prevents a flash of the wrong colours, a feature-flag read, an experiment hook. The price is that those bytes are part of every HTML response, so they are paid again on every navigation and can never be cached or shared across pages; they grow the document, which is on the critical path; they are invalidated whenever anything else on the page changes; and under a strict Content-Security-Policy they need a nonce or a hash. My rule of thumb is that inline scripts stay in the low hundreds of bytes and are counted, not accumulated — the moment a snippet grows into a library, it belongs in a cacheable file.

code

html · 4 lines
html
<script nonce="r4nd0m-per-response">
  const t = localStorage.getItem('theme');
  if (t) document.documentElement.classList.add('theme-' + t);
</script>

go deeper

for a junior

Know that inlining means the code travels inside the HTML, so it arrives immediately but is downloaded again on every page. Be able to say that this suits a few lines, not a library.

for a middle

Explain both sides concretely: one saved round trip against lost independent caching, a larger critical-path document, and execution added before paint. Naming the Content-Security-Policy nonce or hash requirement shows you have shipped this.

for a senior

Show judgment about where the line sits and how you stop it drifting — a size limit, a budget the snippet counts against, and a review whenever something is added to it. Be ready to describe an inline snippet you removed and what it bought back.

for a principal

Own the policy rather than the individual call: what is allowed inline, who approves additions, and how nonce generation interacts with HTML caching at the edge. Be prepared to argue the security posture and the performance posture together, since they pull in the same direction here.

## The one thing inlining buys An external script costs a URL lookup, possibly a new connection, and a round trip before its first byte arrives. On a slow mobile connection that overhead can exceed 200 ms regardless of how small the file is. Inlining removes it entirely: the code arrives inside the document that is already being downloaded. That is the whole benefit, and it only matters when the code must run at a moment early enough that a round trip would be visible. ## What legitimately qualifies - **Anti-flash snippets.** Reading a stored theme, density or locale preference and applying a class to the root element before content paints. If this arrives late, the user sees the wrong colours and then a jump. - **Experiment and personalisation hooks.** Code that decides which variant of the visible content is shown. Load it late and you get a visible flicker of the control variant. - **Early instrumentation.** A handful of lines that start a timer or register an observer so that later measurement has a reference point. - **Bootstrap glue.** A few lines that read configuration the server rendered into the page and hand it to code that loads later. What these share: they are hundreds of bytes, not kilobytes; they must run before something the user can see; and they are page-specific enough that caching them separately would not have paid off anyway. ## What inlining costs **No independent caching.** An external file is fetched once and reused across every page and every visit until its URL changes. Inlined bytes are re-sent with every HTML response, to every visitor, on every navigation. A 30 KB library inlined on a ten-page journey is 300 KB transferred instead of 30 KB. **Coupled invalidation.** HTML is usually served with little or no caching because it changes. Anything inlined inherits that. Conversely, changing one line of the snippet means the whole document body must be re-sent — there is no way to invalidate the script alone. **A bigger document on the critical path.** The HTML response is the very first thing the browser needs and everything else is discovered by parsing it. Padding it with script bytes delays the discovery of the stylesheet and the hero image, which is the opposite of what you were trying to achieve. **Execution where it sits.** Inline code runs at the point in the document where it appears, and the time it takes is added directly to the time before paint. That is acceptable for a few lines of `localStorage` access; it is not acceptable for anything that loops over the DOM. **Security policy friction.** Under a Content-Security-Policy that constrains `script-src`, inline code does not run unless the policy allows it explicitly — normally a per-response `nonce` attribute matching the policy, or a `sha256` hash of the exact script contents listed in the policy. The escape hatch, `'unsafe-inline'`, defeats a major part of the policy's purpose, so it is not a legitimate answer. This is real operational cost: nonces must be generated per response and cannot be cached, and hashes must be regenerated every time the snippet changes. ```html <script nonce="r4nd0m-per-response"> const t = localStorage.getItem('theme'); if (t) document.documentElement.classList.add(`theme-${t}`); </script> ``` **Weaker tooling.** Inline code is harder to source-map, lint, type-check and test than a file that goes through the normal build, and it tends to drift out of sync with the codebase it duplicates logic from. **No integrity check.** The `integrity` attribute, which pins an external file to a known hash, has no meaning for code that is already part of the document — the document's own integrity is the only protection. ## Drawing the line A practical policy: inline scripts must run before paint, must be small enough to read in one screen, must be counted in the page's critical-path budget, and must be justified individually rather than accumulated. Everything else is an external file, where caching, integrity, tooling and independent invalidation all work in your favour. The failure mode teams fall into is not one bad decision but drift: a snippet that was 200 bytes acquires a helper, then a polyfill, then a fallback path, and two years later there are four kilobytes of uncacheable code in every response that nobody has read. ## The related move that is not the same thing Inlining a script is often confused with inlining other critical resources, and they trade differently. A script's inlining tradeoff is dominated by execution timing and policy friction; the general shape — save a round trip, lose independent caching — is the same, but the sizes at which it stops paying off differ, so decide it per resource rather than adopting one house rule for everything.

  • The inline snippet on your page has grown to 20 KB. What changes about the decision?
    At that size the round trip you saved is dwarfed by the bytes you re-send. Twenty kilobytes now travel with every HTML response instead of being fetched once and cached, the document takes longer to arrive and parse, and the execution time is added directly before paint. Move it into a file, and if part of it genuinely must run before paint, split that part out and keep only it inline.
  • How do you keep an inline script working under a strict Content-Security-Policy without weakening the policy?
    Generate a fresh nonce for each response, put it in both the policy header and the script tag's nonce attribute, and never cache that HTML as-is. Alternatively, list a sha256 hash of the exact script contents in the policy, which suits build-time-fixed snippets but must be regenerated on every change. Reaching for 'unsafe-inline' removes the protection the policy exists to provide.
  • Does inlining a script help or hurt Largest Contentful Paint?
    It can do either. A tiny snippet that prevents a re-render or a flicker helps, because the largest element paints once and correctly. A large inline block hurts twice: it delays the HTML response that must arrive before anything else is discovered, and its execution occupies the main thread during the window in which the LCP element should be painted.

saying these in an interview costs you the question

  • Inlines whole libraries to 'save a request'
  • Thinks inline code skips parse and execution cost
  • Forgets inlined bytes re-ship with every HTML response
  • Reaches for unsafe-inline when CSP blocks a snippet
  • Claims inlining is always faster than an external file

context