1. Courses
  2. Developer Foundations
  3. JavaScript in the Real World
  4. Stop 5

35 minAdvanced

Performance and memory in JavaScript

Most slow JavaScript is slow for a boring reason: one loop does far too much work, or memory grows until the process crashes. This lesson shows what the engine does with your code, how the garbage collector decides what to free, and how to find a leak or a hot function with real tools instead of guessing.

By the end you can

  • Explain JIT compilation and hidden classes in a few sentences, and keep object shapes consistent
  • Say which objects the garbage collector can free, using the idea of reachability
  • Recognise the four common leaks: unbounded caches, listeners, closures and timers
  • Find a leak by comparing two heap snapshots, and fix it with a bounded LRU cache
  • Profile a slow function with node --prof or --cpu-prof, and prove the fix with numbers

The everyday picture

Think of the stockroom of a Mangrove Market store after a big delivery. Staff unpack cartons and the empty ones pile up. Every evening a cleaner walks through and throws away every carton that nobody needs. He has one rule: a carton stays if someone has written "keep" on it, for example because it must go back to the supplier.

Now imagine one staff member writes "keep" on every carton, just in case. The cleaner cannot remove any of them. The pile grows a little each day. First people walk around it and work gets slower. Then one day there is no room to receive the next delivery. Nothing broke at once; the stockroom filled up slowly.

That is a JavaScript memory leak. The cleaner is the garbage collector, the "keep" label is a reference from something that is still alive, and the delivery that cannot be received is the crash at 2 a.m. after the server has been running for a week.

What V8 does with your code

is the engine inside Node.js and Chrome. When it gets your file, it first parses the text into a tree, then turns each function into compact that an interpreter (called Ignition) can run at once. Starting fast matters more than running fast for code that runs only once.

While the bytecode runs, V8 watches. It counts how often each function runs and records what kinds of values flow through each line. When a function gets , a turns it into machine code, using what it observed as assumptions: "qty has always been a small integer here". V8 has several compiler tiers (Sparkplug, Maglev and TurboFan in recent versions); the names change, the idea does not. If an assumption breaks later, V8 throws the optimised code away and goes back to bytecode. This is called .

The most useful assumption to understand is the , also called a shape. Objects created with the same properties in the same order share one shape, so r.qty is always at the same position and the machine code can read it directly. V8 remembers the shapes it saw at each property access in an . One shape is fastest. Up to four shapes is still fine. Beyond that the line becomes "megamorphic" and falls back to a slower generic lookup.

We measured this. One million sale rows with the same four fields, summed 20 times. When every row was built in the same field order, the best run took about 265 ms. When the same fields were added in six different orders, it took about 400 ms. Same data, same loop, roughly 1.5 times slower. The fix is cheap: build objects in one place (a factory function or a class), set every field in the constructor, and avoid adding properties later or using delete.

JavaScript
// shapes-check.js: run with  node --allow-natives-syntax shapes-check.js  (debug flag, never in production)
const a = { siteId: 'D14', qty: 5 };
const b = { siteId: 'D20', qty: 9 };     // same keys, same order
const c = { qty: 9, siteId: 'D20' };     // same keys, different order
const d = { siteId: 'D31' }; d.qty = 2;  // qty added later
console.log('a, b same hidden class:', %HaveSameMap(a, b));
console.log('a, c same hidden class:', %HaveSameMap(a, c));
console.log('a, d same hidden class:', %HaveSameMap(a, d));
Output
a, b same hidden class: true
a, c same hidden class: false
a, d same hidden class: false

Keep this in proportion. Shapes matter in a hot loop over a million rows. They do not matter in a controller that handles one request. The bigger wins almost always come from doing less work, which the profiling section shows.

Quick check

Which object shares a hidden class with { siteId: 'D14', qty: 5 }?

Loading the rest of the lesson…