JavaScript Performance Quiz: V8, Memory and Latency Test
Test JavaScript performance with 12 questions and three programs on V8 hidden classes, benchmarking, garbage collection, memory leaks and p99 latency.
- Course: JavaScript study plan
- Module: Performance and memory
- Kind: Checkpoint — cleared at 70%
- Reading time: 24 min
- Runtime: Node 16
Checkpoint — Performance and memory is the checkpoint that closes the Performance and memory module: a graded quiz and whole-program exercises, passed at 70%.
Instructions
This checkpoint covers V8's pipeline (interpreter, feedback, optimising compiler, deoptimisation), hidden classes and inline caches, elements kinds; benchmarking pitfalls and statistics, profiling and flame charts, complexity before constants; reachability, the generational collector, the leak catalogue and heap-snapshot diagnosis; the habits that survive measurement; and server-side latency — percentiles, round trips, caching layers, pools, event-loop lag, load testing.
How it works. Twelve questions and three programs; 70% on the questions and every program accepted clears the module. The programs model the engine's and the collector's rules rather than timing anything — timings would differ on every machine, and the rules are what you need to reason with.
Before you start, make sure you can answer:
- What a hidden class is and what breaks sharing; monomorphic versus megamorphic; how elements kinds transition.
- Why a naive benchmark lies (warm-up, dead code, folding, GC); median versus mean; what a flame chart's width means.
- What keeps an object alive; young versus old generation; the seven leak patterns; how to read a snapshot comparison.
- The complexity table (Set for membership, no
shiftqueues, no spread inreduce); what to batch; when to use a worker. - Why p99 matters; the round-trip arithmetic of sequential awaits; cache-aside versus SWR; what event-loop lag indicates.
The programs are a hidden-class and inline-cache simulator that reports shapes and IC states per call site, a generational garbage-collector simulator with promotion and mark-sweep, and a request-trace analyser that computes per-route percentiles, SLO burn and N+1 suspects.
Common questions
What breaks hidden class sharing in V8?
Adding properties in a different order, adding them conditionally or after construction, and delete — each gives objects different shapes or drops them into dictionary mode. Initialise every property in the constructor or literal, in the same order, every time.
What keeps an object alive in JavaScript?
Any path of references from a root — the global object, the current call stack or an engine reference — through properties, closure scopes, array elements, Map and Set entries or prototype links. An object with no such path is garbage and will be collected eventually.
What does the width of a bar in a flame chart mean?
Width is time: a wide bar is a function, together with everything it called, that ran for a long share of the recording, so the widest bars are the hot spots. Depth shows call nesting, not cost.
Exercises
Hidden classes and inline caches, simulated
Commands: class <Name>: <prop> <prop> … defines a constructor that assigns those properties in order; new <Name> <obj> instantiates; set <obj> <prop> adds a property later (a transition off the constructor's shape); delete <obj> <prop> drops the object to dictionary mode; site <name> <obj> … is a property-access site and the objects it has seen. Maintain a transition tree from S0 where the same property added to the same shape reaches the same child. Print transition tree: then the tree indented two spaces per level as S<n> {props} ({} for the root); objects: <obj>=<shape|dictionary> …; each site as site <name>: <state> [<shapes seen>] where the state is monomorphic, polymorphic (2–4), megamorphic (more), or megamorphic (dictionary-mode object) when any object seen is in dictionary mode; and shapes created=<n> uniformClasses=<classes whose live non-dictionary instances all share one shape, or ->.
Example: Point and Vec both declaring x y, p2 given z later, p3 with x deleted →
transition tree:
S0 {}
S1 {x}
S2 {x,y}
S3 {x,y,z}
objects: p1=S2 p2=S3 v1=S2 p3=dictionary
site readX: polymorphic [S2,S3]
site same: monomorphic [S2]
site broken: megamorphic (dictionary-mode object) [S2,dictionary]
site one: monomorphic [S2]
shapes created=3 uniformClasses=Vec
Two classes with the same property order share one shape; one late set forked Point's instances apart.
A generational garbage collector
Simulate V8's two generations. Commands: alloc <id> <bytes> (young, age 0); ref <from|root> <to>; unref <from|root> <to>; scavenge — a young-generation collection: mark from the roots and from every old-space object (the remembered set — a scavenge never looks inside old space, so a dead old object still keeps its young children alive), free unreachable young objects, age the survivors and promote those reaching age 2; print scavenge #<n>: freed=<ids or -> promoted=<ids or -> young=<bytes>B old=<bytes>B. major — mark from the real roots over both spaces and sweep: major #<n>: freed=<ids or -> young=<>B old=<>B. stats → stats: objects=<> young=<>B old=<>B scavenges=<> majors=<>.
Example: a (rooted) and b survive two scavenges and are promoted; c is then allocated under b, a drops b, and a scavenge runs →
scavenge #4: freed=- promoted=c young=0B old=170B
…
major #1: freed=b,c young=0B old=100B
c was unreachable from the roots yet survived and was promoted, because dead old b counts as a root during a scavenge. Only the major collection frees them both.
Request-trace analyser
Line 1 is slo=<ms> target=<percent> queries=<limit>; each following line is one request: <route> <ms> <status> <queries>. Group by route and print, sorted by p95 descending, <route>: n=<> p50=<> p95=<> p99=<> errors=<status ≥ 500> n+1suspects=<requests over the query limit> maxQueries=<> (nearest-rank percentiles). Then slowest by p95: <route>; SLO <ms>ms @ <target>%: <xx.x>% good -> <met|missed>; error budget used=<n>% where a request is good when within the SLO and not a 5xx, and budget used = bad requests ÷ allowed bad (n × (1 − target/100)); and the single worst offender: N+1 candidate: <route> ran <q> queries in one request (limit <limit>) - batch them or N+1: none over the limit.
Example: ten requests over /home, /orders (one 900 ms request with 45 queries, one 500) and /search at slo=300 target=95 queries=10 →
/orders: n=4 p50=260 p95=900 p99=900 errors=1 n+1suspects=1 maxQueries=45
/home: n=3 p50=120 p95=140 p99=140 errors=0 n+1suspects=0 maxQueries=3
/search: n=3 p50=80 p95=95 p99=95 errors=0 n+1suspects=0 maxQueries=1
slowest by p95: /orders
SLO 300ms @ 95%: 80.0% good -> missed; error budget used=400%
N+1 candidate: /orders ran 45 queries in one request (limit 10) - batch themIn this module: Performance and memory
- How V8 runs your code — parsing, tiers, hidden classes and inline caches
- Measure first — benchmarks that lie, profiles that do not, and reading the numbers
- Memory and garbage collection — generations, reachability, and the leaks that survive it
- Writing fast JavaScript — the habits that survive measurement
- Server and network performance — latency budgets, percentiles, caching and the event loop under load
- Checkpoint — Performance and memory (this lesson)
← Server and network performance — latency budgets, percentiles, caching and the event loop under load · The template — running a JavaScript coding round from your side of the table →