Performance Profiling & Optimization
📖 Concept
Performance optimization starts with measurement, not guesswork. Node.js provides built-in tools and third-party solutions for identifying bottlenecks.
Key performance metrics:
| Metric | Target | Tool |
|---|---|---|
| Response time (p95) | < 200ms | APM, custom middleware |
| Throughput (req/s) | Depends on workload | Load testing (autocannon) |
| Memory usage | < 500MB RSS | process.memoryUsage() |
| Event loop lag | < 10ms | perf_hooks, Clinic.js |
| CPU usage | < 70% | os.cpus(), top/htop |
Profiling tools:
node --prof— V8 CPU profiler (generates tick files)node --inspect— Chrome DevTools profiler (Memory, CPU, Performance)- Clinic.js — Automated performance diagnosis (Doctor, Flame, Bubbleprof)
- autocannon — HTTP load testing (like Apache Bench but better)
perf_hooks— Precise performance measurement API
Common performance bottlenecks:
- Synchronous operations —
readFileSync,JSON.parseon large data - Memory leaks — global arrays, event listeners, closures
- N+1 queries — fetching related data in loops
- Missing indexes — database queries doing full table scans
- Large payloads — sending unnecessary data in API responses
- No caching — re-computing or re-fetching unchanged data
🏠 Real-world analogy: Performance profiling is like a doctor's checkup. You measure vital signs (metrics), run diagnostics (profiling), identify the problem (bottleneck), and prescribe treatment (optimization). Guessing without measuring is like taking random medicine.
💻 Code Example
1// Performance Profiling & Optimization23const { performance, PerformanceObserver } = require("perf_hooks");45// 1. Measure function performance6function measureExecution(label, fn) {7 const start = performance.now();8 const result = fn();9 const duration = performance.now() - start;10 console.log(`[${label}] ${duration.toFixed(2)}ms`);11 return result;12}1314// Async version15async function measureAsync(label, fn) {16 const start = performance.now();17 const result = await fn();18 const duration = performance.now() - start;19 console.log(`[${label}] ${duration.toFixed(2)}ms`);20 return result;21}2223// 2. Memory monitoring24function logMemory(label = "") {25 const usage = process.memoryUsage();26 console.log(`Memory ${label}:`, {27 rss: `${(usage.rss / 1024 / 1024).toFixed(1)}MB`, // Total allocated28 heapUsed: `${(usage.heapUsed / 1024 / 1024).toFixed(1)}MB`, // JS objects29 heapTotal: `${(usage.heapTotal / 1024 / 1024).toFixed(1)}MB`,30 external: `${(usage.external / 1024 / 1024).toFixed(1)}MB`, // C++ objects31 });32}3334// 3. Event loop monitoring35function monitorEventLoop() {36 let lastCheck = Date.now();3738 setInterval(() => {39 const now = Date.now();40 const lag = now - lastCheck - 1000; // Expected 1000ms interval41 lastCheck = now;4243 if (lag > 50) {44 console.warn(`Event loop lag: ${lag}ms`);45 }46 }, 1000);47}4849// 4. Response time middleware50function responseTimeMiddleware(req, res, next) {51 const start = process.hrtime.bigint();5253 res.on("finish", () => {54 const duration = Number(process.hrtime.bigint() - start) / 1e6; // Convert to ms55 res.setHeader("X-Response-Time", `${duration.toFixed(2)}ms`);5657 // Alert on slow responses58 if (duration > 1000) {59 console.warn(`Slow response: ${req.method} ${req.originalUrl} - ${duration.toFixed(0)}ms`);60 }61 });6263 next();64}6566// 5. Optimization patterns6768// ❌ SLOW: Creating new RegExp in every call69function validateEmailSlow(email) {70 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);71}7273// ✅ FAST: Compile regex once74const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;75function validateEmailFast(email) {76 return EMAIL_REGEX.test(email);77}7879// ❌ SLOW: String concatenation in loops80function buildStringSlow(items) {81 let result = "";82 for (const item of items) {83 result += `Item: ${item}\n`; // Creates new string each iteration84 }85 return result;86}8788// ✅ FAST: Array join89function buildStringFast(items) {90 return items.map((item) => `Item: ${item}`).join("\n");91}9293// ❌ SLOW: JSON.parse/stringify for deep cloning94function deepCloneSlow(obj) {95 return JSON.parse(JSON.stringify(obj)); // Expensive!96}9798// ✅ FAST: structuredClone (Node.js 17+)99function deepCloneFast(obj) {100 return structuredClone(obj);101}102103// 6. Load testing with autocannon104// npm install -g autocannon105// autocannon -c 100 -d 10 http://localhost:3000/api/users106// -c 100: 100 concurrent connections107// -d 10: run for 10 seconds108109module.exports = { measureAsync, logMemory, monitorEventLoop, responseTimeMiddleware };
🏋️ Practice Exercise
Exercises:
- Add response time headers and logging to an Express API — alert on responses > 500ms
- Profile memory usage during a load test — identify and fix any memory leaks
- Use Clinic.js Doctor to diagnose performance issues in a sample application
- Run autocannon against your API and optimize until you achieve 1000+ req/s
- Compare JSON.parse vs streaming JSON parsing for a 100MB file
- Implement event loop lag monitoring and alerting
⚠️ Common Mistakes
Optimizing without measuring first — always profile before optimizing; premature optimization wastes time on non-bottlenecks
Using synchronous operations in request handlers —
readFileSync,crypto.pbkdf2Syncblock the entire event loop; use async versionsAccumulating data in memory (arrays, maps, caches) without limits — set maximum sizes and eviction policies
Not using database connection pooling — creating a new connection per request is extremely expensive; reuse connections
Sending entire database records in API responses — select only needed fields; reduce payload size
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Performance Profiling & Optimization. Login to unlock this feature.