Performance Diagnostics
Measure before optimizing - timers, memory samples, and event-loop healthy patterns. Results appear in the same fence: same-line // comments when short, multiline // blocks below the sample when not.
Search across all documentation pages
Measure before optimizing - timers, memory samples, and event-loop healthy patterns. Results appear in the same fence: same-line // comments when short, multiline // blocks below the sample when not.
High-resolution durations with performance.now().
import { performance } from "node:perf_hooks";
const t0 = performance.now();
let s = 0;
for (let i = 0; i < 1000; i++) s += i;
const ms = performance.now() - t0;
ms >= 0 // true
s // 499500Record distributions of latency for hot paths.
import { createHistogram } from "node:perf_hooks";
const h = createHistogram();
h.record(10);
h.record(20);
h.min // 10
h.max // 20Track RSS and heap during load tests.
const { rss, heapUsed } = process.memoryUsage();
rss > 0 // true
heapUsed > 0 // trueGenerate CPU profiles with CLI flags for Chrome DevTools.
// node --cpu-prof app.js
// node --heap-prof app.js
const flags = ["--cpu-prof", "--heap-prof"];
flags[0] // "--cpu-prof"Never use sync fs APIs on the request path - they block the event loop.
// Bad in servers: readFileSync
// Good: await readFile(...)
import { readFile } from "node:fs/promises";
typeof readFile // "function"Use User Timing marks for multi-step operations.
import { performance } from "node:perf_hooks";
performance.mark("a-start");
performance.mark("a-end");
performance.measure("a", "a-start", "a-end");
const entries = performance.getEntriesByName("a");
entries[0].duration >= 0 // trueDetect event loop lag under load.
import { monitorEventLoopDelay } from "node:perf_hooks";
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
await new Promise((r) => setTimeout(r, 50));
h.disable();
h.mean >= 0 // true (nanoseconds)Large JSON.parse can block - stream or move to a worker for huge payloads.
const data = JSON.parse('{"n":1}');
data.n // 1
// fine for small/medium payloadsAvoid allocating huge buffers per request when a pooled size works.
const pool = Buffer.alloc(64 * 1024);
pool.length // 65536Repeated DNS lookups add latency - connection reuse via agents helps.
// Prefer keep-alive fetch/http agents for many calls to same host
const keepAlive = true;
keepAlive // trueTools like Clinic.js/0x complement built-in profiles for production-like loads.
// clinic doctor -- node app.js
const tip = "clinic doctor -- node app.js";
tip.startsWith("clinic") // trueManual GC is diagnostic-only (--expose-gc) - never rely on it in production logic.
// node --expose-gc
// global.gc?.();
typeof (global as any).gc // "undefined" unless --expose-gcStream large responses instead of buffering entire payloads in memory.
import { Readable } from "node:stream";
const body = Readable.from(["chunk"]);
const parts: string[] = [];
for await (const c of body) parts.push(String(c));
parts // ["chunk"]Prefer batch queries over N+1 await loops on the hot path.
const ids = [1, 2, 3];
// await db.query("select * from t where id = any($1)", [ids]);
ids.length // 3Micro-benchmarks lie - measure under realistic concurrency and payload sizes.
// Use autocannon/wrk against the real HTTP handler, not only a tight loop.
const tool = "autocannon";
tool.length // 10Stack versions: Node.js 24.18.0 (LTS line 24) · TypeScript 5.6+ · npm 10+
Reviewed by Chris St. John·Last updated Jul 18, 2026