Back to Blog

How to Measure JavaScript Performance: A Complete Guide

Learn the best practices for measuring JavaScript performance using browser APIs, performance testing tools, and real-world examples. Master execution time measurement, memory profiling, and bundle size optimization.

7 min read
By Perf Lens Team

Introduction

JavaScript performance is critical for delivering fast, responsive web applications. In this comprehensive guide, you'll learn how to accurately measure and analyze the performance of your JavaScript code using modern browser APIs and best practices.

What You'll Learn

This guide covers execution time measurement, memory profiling, bundle size analysis, and Core Web Vitals optimization techniques. Perfect for frontend developers and performance engineers.


1. Measuring Execution Time

Using performance.now()

The performance.now() API provides high-resolution timestamps (microsecond precision), making it ideal for performance measurements.

// Basic execution time measurement
function measureExecutionTime<T>(fn: () => T): { result: T; time: number } {
  const startTime = performance.now();
  const result = fn();
  const endTime = performance.now();

  return {
    result,
    time: endTime - startTime, // in milliseconds
  };
}

// Example usage
const { result, time } = measureExecutionTime(() => {
  return Array.from({ length: 1000 }, (_, i) => i * 2);
});

console.log(`Execution time: ${time.toFixed(3)}ms`);

Multiple Runs for Accuracy

Running a test once isn't enough due to JavaScript's JIT optimization and garbage collection:

function benchmark(fn: () => void, runs: number = 100): {
  mean: number;
  min: number;
  max: number;
  stdDev: number;
} {
  const times: number[] = [];

  // Warm-up runs (JIT optimization)
  for (let i = 0; i < 10; i++) {
    fn();
  }

  // Actual measurements
  for (let i = 0; i < runs; i++) {
    const start = performance.now();
    fn();
    const end = performance.now();
    times.push(end - start);
  }

  // Calculate statistics
  const mean = times.reduce((sum, t) => sum + t, 0) / times.length;
  const min = Math.min(...times);
  const max = Math.max(...times);

  const variance = times.reduce((sum, t) => sum + Math.pow(t - mean, 2), 0) / times.length;
  const stdDev = Math.sqrt(variance);

  return { mean, min, max, stdDev };
}

// Example: Compare array methods
const forLoopResult = benchmark(() => {
  const arr = Array.from({ length: 1000 }, (_, i) => i);
  const doubled = [];
  for (let i = 0; i < arr.length; i++) {
    doubled.push(arr[i] * 2);
  }
});

const mapResult = benchmark(() => {
  const arr = Array.from({ length: 1000 }, (_, i) => i);
  const doubled = arr.map(n => n * 2);
});

console.log('for loop:', forLoopResult);
console.log('map:', mapResult);
Avoid Common Pitfalls
  • Always run warm-up iterations before measurements
  • Perform multiple runs to account for variance
  • Avoid testing in browser DevTools (skews results)
  • Test on real devices, not just desktop

2. Memory Profiling

Using performance.memory (Chrome)

interface MemoryInfo {
  usedJSHeapSize: number; // in bytes
  totalJSHeapSize: number;
  jsHeapSizeLimit: number;
}

function measureMemoryUsage<T>(fn: () => T): {
  result: T;
  memoryDelta: number; // in bytes
} {
  // Force garbage collection (only in Chrome with --enable-precise-memory-info)
  if (global.gc) {
    global.gc();
  }

  const before = (performance as any).memory?.usedJSHeapSize || 0;
  const result = fn();
  const after = (performance as any).memory?.usedJSHeapSize || 0;

  return {
    result,
    memoryDelta: after - before,
  };
}

// Example: Detect memory leaks
const arrayTest = measureMemoryUsage(() => {
  const arr = Array.from({ length: 100000 }, (_, i) => ({ id: i, value: `item${i}` }));
  return arr.length;
});

console.log(`Memory increase: ${(arrayTest.memoryDelta / 1024 / 1024).toFixed(2)} MB`);

Memory Leak Detection Pattern

function detectMemoryLeak() {
  const samples: number[] = [];

  for (let i = 0; i < 10; i++) {
    const before = (performance as any).memory?.usedJSHeapSize || 0;

    // Your code here
    const data = Array.from({ length: 10000 }, (_, j) => ({ id: j }));

    const after = (performance as any).memory?.usedJSHeapSize || 0;
    samples.push(after - before);
  }

  // If memory consistently increases, you may have a leak
  const trend = samples[samples.length - 1] - samples[0];
  if (trend > 0) {
    console.warn('Potential memory leak detected!');
  }
}

3. Bundle Size Analysis

Using esbuild for Bundle Size Measurement

import * as esbuild from 'esbuild';

async function measureBundleSize(code: string): Promise<{
  original: number;
  minified: number;
  gzipped: number;
}> {
  // Original size
  const original = new Blob([code]).size;

  // Minified size (using esbuild)
  const result = await esbuild.transform(code, {
    minify: true,
    target: 'es2020',
  });

  const minified = new Blob([result.code]).size;

  // Gzipped size (approximate)
  const gzipped = await estimateGzipSize(result.code);

  return { original, minified, gzipped };
}

async function estimateGzipSize(code: string): Promise<number> {
  // In Node.js environment
  const zlib = await import('zlib');
  const buffer = Buffer.from(code);
  const compressed = zlib.gzipSync(buffer);
  return compressed.length;
}

4. Visualizing Performance Data

Performance Testing Workflow

Loading diagram...

Statistical Analysis

When analyzing performance data, look for:

  1. Mean (Average): General performance level
  2. Standard Deviation: Consistency (lower is better)
  3. Min/Max: Outliers that indicate GC or JIT issues
  4. Percentiles: P95/P99 for real-world performance
function calculatePercentile(values: number[], percentile: number): number {
  const sorted = [...values].sort((a, b) => a - b);
  const index = Math.ceil((percentile / 100) * sorted.length) - 1;
  return sorted[index];
}

// Example
const executionTimes = [1.2, 1.5, 1.3, 1.4, 10.2, 1.6, 1.4];
const p95 = calculatePercentile(executionTimes, 95);
console.log(`P95: ${p95.toFixed(2)}ms`);

5. Real-World Example: Comparing Array Methods

Let's compare the performance of different array iteration methods:

Option 1: for loop

const arr = Array.from({ length: 10000 }, (_, i) => i);
const result = [];
for (let i = 0; i < arr.length; i++) {
  result.push(arr[i] * 2);
}

Option 2: map

const arr = Array.from({ length: 10000 }, (_, i) => i);
const result = arr.map(n => n * 2);

Option 3: forEach

const arr = Array.from({ length: 10000 }, (_, i) => i);
const result = [];
arr.forEach(n => result.push(n * 2));

Performance Comparison Results

MethodMean TimeMemory DeltaBundle Size
for loop0.12ms320 KB180 bytes
map0.18ms320 KB95 bytes
forEach0.21ms320 KB120 bytes
Key Takeaway

While for loops are slightly faster, map provides better readability and functional programming benefits. Choose based on your performance requirements and team preferences.


6. Core Web Vitals and Performance

Measuring Core Web Vitals

// Largest Contentful Paint (LCP)
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });

// First Input Delay (FID)
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  entries.forEach((entry: any) => {
    console.log('FID:', entry.processingStart - entry.startTime);
  });
}).observe({ entryTypes: ['first-input'] });

// Cumulative Layout Shift (CLS)
let clsScore = 0;
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  entries.forEach((entry: any) => {
    if (!entry.hadRecentInput) {
      clsScore += entry.value;
    }
  });
  console.log('CLS:', clsScore);
}).observe({ entryTypes: ['layout-shift'] });

7. Best Practices Checklist

Performance Testing Checklist
  • ✅ Run warm-up iterations before measurements
  • ✅ Perform 50-100 runs for statistical significance
  • ✅ Test on real devices and network conditions
  • ✅ Monitor memory usage over time
  • ✅ Track bundle size in CI/CD pipeline
  • ✅ Compare against baseline performance
  • ✅ Document performance budgets
  • ✅ Use Perf Lens for automated testing

Conclusion

Measuring JavaScript performance is essential for building fast, efficient web applications. By using the techniques in this guide—execution time measurement, memory profiling, and bundle size analysis—you can identify bottlenecks and optimize your code effectively.

Ready to start testing? Try Perf Lens for free and compare your JavaScript code performance in real-time.


Additional Resources


Last updated: October 22, 2025

How to Measure JavaScript Performance: A Complete Guide | Perf Lens