Reading — step 1 of 7
Learn
V8 (Chrome, Node, Edge, Deno) is one of the most aggressive JIT compilers ever shipped. To get fast JavaScript, you write code V8 can specialize. The rules below also apply to JavaScriptCore (Safari) and SpiderMonkey (Firefox) with minor variations.
How V8 runs your code
- Ignition — bytecode interpreter, runs cold code
- Sparkplug — fast non-optimizing baseline compiler (V8 9.1+)
- TurboFan — optimizing compiler, kicks in for hot code
- Maglev — mid-tier optimizer (V8 11.5+)
The JIT collects type info on every property access and call, then makes ASSUMPTIONS to specialize. If assumptions break, the code is deoptimized back to bytecode — slow.
Hidden classes (a.k.a. shapes / maps)
V8 internally treats objects with the same structure as having the same hidden class. Property accesses become a single offset lookup.
Adding properties later in different orders creates DIFFERENT hidden classes:
Hot code accessing obj.x across thousands of objects with various hidden classes ("polymorphic") is much slower than monomorphic.
Rule: initialize all properties in the constructor, in the same order. Don't add new properties later.
Inline caches and polymorphism
For obj.x in hot code:
- Monomorphic — always the same hidden class. Fastest.
- Polymorphic — 2-4 hidden classes. ~3x slower.
- Megamorphic — 5+ hidden classes. Falls back to dictionary lookup. ~10x slower.
Keep types stable through hot loops.
Type stability
Each unique type pattern is a separate optimization tier. Mixing types invalidates the optimization.
Common deoptimizations
- Out-of-bounds array access — invalidates the JIT's bounds-check assumptions
- Sparse arrays /
delete arr[i]— converts dense array to dictionary mode argumentsobject usage — spread/rest is fastr (function f(...args))try/catchin hot functions — historically prevented optimization (less true now)withstatement — never optimized; basically forbidden in real codeeval— same- NaN-mixing in numeric arrays — V8 may demote SMI arrays to double arrays
Memory and allocation
Every new, every literal {...}, [...], 'a' + 'b' allocates. The GC walks live objects periodically — pressure causes pauses.
Reduce allocations in hot paths:
- Reuse buffers:
Buffer.alloc()once, reuse with.fill(0)between uses - Object pooling for short-lived objects
array.length = 0instead ofarray = []
Strings: + concatenation in a loop allocates O(n²). Use Array.join('') or String.prototype.concat chains:
Profiling
- Browser DevTools → Performance tab → record
- Node:
node --prof script.js→node --prof-process isolate-*.log - Inspector:
node --inspect-brk script.jsthen connect Chrome DevTools --trace-opt --trace-deopt— V8 flags to see what's being optimized/deopt'd
When to bother
The vast majority of JavaScript code doesn't need any of this. Worry about it for:
- Inner loops in render hot paths
- Data structure libraries
- Servers with thousands of QPS
For application code, write clear code and measure. Premature optimization in V8 is a famous waste of time.
Common mistakes
- Optimizing without profiling — V8 is very smart; bottlenecks are rarely where you think.
- Believing 'tricks' from old blog posts — V8 has changed dramatically. Patterns from 2015 may now be inverted.
- Reading micro-benchmarks — they don't capture the JIT warming up. Benchmark with realistic workloads.
- Adding properties to hot-path objects late — destroys hidden class sharing.
- Mixing types in hot arrays — converts SMI/double-only arrays to slower dictionary mode.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…