Why JavaScript runtimes diverge on cold-start latency
Published 2026-04-22 · ~3 min read
Cold-start latency is one of the most-quoted but least-understood metrics in the JavaScript runtime conversation. When V8, JavaScriptCore, and the newer engines like Bun's are benchmarked from a cold process, the gap between them can swing from ten milliseconds to several hundred milliseconds depending on what exactly is being measured. This article walks through the four phases of a typical cold start and explains why two engines that look similar on a synthetic benchmark can diverge wildly on a real application.
Phase 1: process boot
Every runtime begins by loading its binary into memory and initialising its allocator. Bun has aggressively trimmed this phase by statically linking JavaScriptCore and skipping V8's snapshot deserialisation, which historically dominated Node.js boot. Deno made a similar choice when moving to a single static binary. Anecdotally, the wall-clock difference here is around 8–15 ms on a modern Mac.
Phase 2: parse and bytecode emit
Once running, the engine has to parse your entry file and any of its synchronous imports. The cost is roughly linear in the number of source bytes, but the constant factor differs by 3× between engines. JavaScriptCore's two-pass parser is faster on small files; V8's TurboFan pipeline catches up on larger workloads through caching.
Phase 3: module graph resolution
This is the phase that surprises most developers. Resolving 400 small CommonJS or ESM modules can take longer than parsing them, because every require walks the file system. Bun's module resolver is faster mostly because it does fewer stat calls, not because of any clever algorithm.
Phase 4: first user code
By the time your main() runs, three quarters of the cold-start budget has typically already been spent. What remains is JIT warm-up, which is highly workload-dependent. Long-running servers will eventually amortise this; short-lived CLIs will not.
Takeaways
The right way to compare runtimes is to publish a per-phase breakdown rather than a single aggregate number. Most of the public benchmarks fail this test, which is why the discourse around "X is faster than Y" tends to confuse more than it clarifies.