20 V8 Internals That Explain Why Your JavaScript Runs Slow
JavaScript performance bottlenecks rarely show up in synthetic benchmarks. They surface when object shapes diverge across thousands of iterations, when a single `delete` or `arguments` reference deopts a hot function, or when a closure pins a large array in memory. Knowing these V8 internals turns guesswork optimization into a checklist of patterns to avoid.
V8's compilation pipeline moves code from parser to AST, through Ignition's bytecode interpreter, and into TurboFan's optimized machine code only when functions run hot. That pipeline breaks whenever code changes object shapes, mixes types, or uses legacy patterns like `delete`, `arguments`, `for-in`, and `eval`. Each break triggers a deopt that drops execution back to bytecode, often 10x slower.
Hidden classes are the engine's way of tracking object property layouts. Two objects with the same properties added in different orders get different hidden classes, which defeats inline caching and forces repeated lookups. The same mechanism makes `delete` a performance killer: removing a property creates a new hidden class transition instead of freeing memory, so setting to `undefined` or `null` keeps the shape stable.
Arrays have their own optimization tiers. Packed, single-type arrays stay in the fastest SMI or DOUBLE modes; sparse arrays or mixed-type arrays fall into HOLEY modes that slow every operation. For large dynamic key-value stores, Map outperforms Object because it avoids hidden class churn entirely. WeakMap and WeakRef provide garbage-collector-friendly alternatives to strong-reference caches that would otherwise leak memory.
The performance advice around `try/catch` blocking TurboFan optimization is largely obsolete since V8 6, yet it persists in developer folklore. The real takeaway is narrower: avoid wrapping the hottest loop in a try/catch, but don't restructure error handling around a constraint that no longer exists.
String concatenation with `+` is a solved problem in modern V8 thanks to the rope data structure. The old advice to use `Array.join()` for performance is now a micro-optimization that rarely matters, yet it still circulates as a best practice.
The hidden class model explains why TypeScript and typed property declarations improve runtime performance even though types are erased: consistent field initialization order in classes produces uniform hidden classes across all instances, maximizing inline cache hits.
V8's array mode transitions are one-way. Once an array becomes HOLEY or PACKED_ELEMENTS, it never upgrades back to a faster mode, so a single sparse assignment or type mix permanently degrades performance for that array.