Skip to content
Performance and V8 Optimization
step 1/7

Reading — step 1 of 7

Learn

~4 min readNumbers and Performance

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

  1. Ignition — bytecode interpreter, runs cold code
  2. Sparkplug — fast non-optimizing baseline compiler (V8 9.1+)
  3. TurboFan — optimizing compiler, kicks in for hot code
  4. 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.

javascript

Adding properties later in different orders creates DIFFERENT hidden classes:

javascript

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

javascript

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
  • arguments object usage — spread/rest is fastr (function f(...args))
  • try/catch in hot functions — historically prevented optimization (less true now)
  • with statement — never optimized; basically forbidden in real code
  • eval — 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 = 0 instead of array = []

Strings: + concatenation in a loop allocates O(n²). Use Array.join('') or String.prototype.concat chains:

javascript

Profiling

  • Browser DevTools → Performance tab → record
  • Node: node --prof script.jsnode --prof-process isolate-*.log
  • Inspector: node --inspect-brk script.js then 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…