The Hidden Cost of JavaScript
Transfer size is the cheapest part of shipping JavaScript. This dossier separates the four costs a script imposes, network, parse, compile, execute, and shows why the last three dominate on the devices most people actually own.
- Level
- RESEARCHER
- Status
- ACTIVE
- Experiments
- 14
- Observations
- 27
Question
Two pages transfer 300KB. One is interactive in 900ms, the other in 5.4s. The network conditions are identical. Where did the difference come from?
Background
A byte of JavaScript is not a byte of an image. An image is decoded once, often on another thread, and never again. A script must be downloaded, parsed into an AST or lazily pre-parsed, compiled to bytecode, executed, and then, if it is hot, re-optimised by the JIT. Every one of those steps happens on the main thread, and every one of them scales with the device's single-core performance.
| Stage | High-end laptop | Mid-range phone |
|---|---|---|
| Transfer (4G) | ~250ms | ~250ms |
| Parse + compile | ~90ms | ~450ms |
| Execute | ~120ms | ~700ms |
| Hydration of the same UI | ~80ms | ~600ms |
Method
- 01Record a trace on a throttled CPU profile (4× or 6×) rather than an unthrottled one.
- 02Separate 'Evaluate Script' from 'Compile Script' in the performance panel, they have different fixes.
- 03Attribute long animation frames with the LoAF API to find which script and which call site.
- 04Compare total blocking time before and after removing a single dependency, not after a batch of changes.
Findings
Finding 1. Parse cost is proportional to code shipped, not code run. V8 pre-parses everything it downloads to find function boundaries, then fully parses functions when first called. Shipping a large library and using one function still pays the pre-parse.
Finding 2. Hydration pays for the UI twice. The server rendered the markup; the client then walks the same tree, creates the same component instances and attaches listeners. Hydration cost scales with component count, not with how much of the page the user can see.
Finding 3. Deferring is not the same as removing. A deferred script still costs its full CPU budget; it just costs it later, often exactly when the user first tries to interact. Moving cost past the LCP measurement improves the score without improving the experience.
Finding 4. The cheapest script is the one never requested. Server-rendered HTML with no client component, or a progressively enhanced form, has a parse cost of zero and an execution cost of zero, at every device tier.
Interpretation
The practical hierarchy is: delete it, then do it on the server, then load it later, then make it smaller. Most optimisation effort goes into the last step, which is the weakest lever in the list.
Why is parse cost so much higher on phones?
Because parsing is single-threaded work bounded by single-core performance.
Limitations
Engine behaviour differs. V8, SpiderMonkey and JavaScriptCore make different lazy-parsing and tiering decisions, and all three change between releases. Treat the mechanism as stable and the numbers as perishable.
Further research
- 01Measure hydration cost per route on a 4× throttled CPU.
- 02Quantify the parse cost of your three largest dependencies individually.
- 03Test whether a route needs client-side JavaScript at all.
References
- 01Addy OsmaniThe cost of JavaScript
- 02V8 blogBlazingly fast parsing
- 03web.devLong Animation Frames API