Writing JIT-friendly JavaScript means writing code that is predictable and consistent, allowing the engine's speculative optimizations to succeed and avoid costly deoptimizations.
Modern JavaScript engines like V8 use Just-In-Time (JIT) compilation to achieve high performance. They monitor code as it runs, make assumptions based on observed patterns (like variable types and object shapes), and generate highly-optimized machine code . JIT-friendly code is code that confirms these assumptions, keeping the code in the fast-optimized path. The core principle is predictability: writing code that is 'boring' for humans but 'delightful' for the JIT compiler .
The most critical rule is to never change the type of a variable or function parameter within a hot function. JIT compilers optimize based on the types they first observe .
If a function like sum(a, b) is called multiple times with numbers, the engine will optimize it for number addition. If it later receives strings, the optimized code must be thrown away (deoptimized), reverting to a slower path .
If you must handle multiple types, consider normalizing the input at the start of the function (e.g., converting parameters to numbers) to present a consistent type to the rest of the optimized function body .
Objects passed to functions should have the same 'shape' (the same properties in the same order). This allows the engine to use Hidden Classes and Inline Caching for near-instant property access .
Always initialize objects with all their properties in a consistent order, preferably in the constructor or object literal. Adding properties later forces a Hidden Class transition, creating a new shape .
Code that consistently uses one object shape is called 'monomorphic' and is the fastest. Code using 2-4 shapes is 'polymorphic' (slower), and code using 5+ shapes is 'megamorphic' (slowest) .
JIT compilers often inline small, frequently-called functions to eliminate call overhead and enable further optimizations .
Keep functions short and focused on a single task. This increases the likelihood that the engine will inline them in hot code paths.
Avoid using language features that can defeat inlining, such as with, eval(), or the arguments object .
Use arrays for their intended purpose: with contiguous, zero-based indexes. Treating an array like an object by adding non-numeric properties changes its internal representation (ElementsKind) and slows it down .
Avoid creating 'holey' arrays (e.g., let arr = [1,,3];). Prefer packed arrays where all elements are present.
Be consistent with the types of elements you store in an array. An array starting with integers (PACKED_SMI_ELEMENTS) that later receives a float will be forced to transition to a more generic, slower representation .
Frequent allocation of temporary objects in hot loops forces the GC to run more often, pausing execution .
Reuse objects where possible, or restructure code to avoid allocation in critical paths. For example, use a simple for loop instead of functional methods like map or filter in extremely performance-sensitive code to avoid creating intermediate arrays and function closures .
The ultimate goal is mechanical sympathy—writing code that works with the engine's optimization strategies, not against them. However, these optimizations should be applied judiciously. Always profile your application to find the real hot paths before optimizing, and use tools like node --trace-opt --trace-deopt or Chrome DevTools to see if your assumptions about what is being optimized are correct . Often, writing simple, clear code is the best first step, as it's easiest for both humans and engines to understand.