// Objects become eligible for GC when unreachablefunction createObjects() { const obj1 = {data: new Array(1000).fill('x')}; const obj2 = {data: new Array(1000).fill('y')}; // Both objects are reachable (local variables) console.log('Objects created'); return obj1; // obj2 becomes unreachable after return}const result = createObjects();// obj2 is now eligible for GC// result (obj1) is still reachable// Make obj1 eligible for GCresult = null; // or let it go out of scope// Circular references (handled by mark-and-sweep)function createCircularRef() { const obj1 = {}; const obj2 = {}; obj1.ref = obj2; // obj1 references obj2 obj2.ref = obj1; // obj2 references obj1 // Both have references to each other // But when function returns, both become unreachable // Mark-and-sweep handles this correctly}createCircularRef(); // Both objects will be GC'd// Closure keeping object alivefunction createClosure() { const largeData = new Array(1000000).fill('data'); return function() { // This closure keeps largeData alive console.log(largeData.length); };}const fn = createClosure();// largeData is kept in memory because closure references it// Release closure and its captured variablesfn = null; // Now largeData can be GC'd// Global variables never collectedwindow.globalData = new Array(1000000); // Stays until page unload// WeakMap/WeakSet allow GCconst cache = new WeakMap();function cacheUserData(user) { // user object is key, data is value cache.set(user, {expensive: 'computation'});}let user = {id: 1, name: 'Alice'};cacheUserData(user);// When user is no longer referenced elsewhereuser = null;// The WeakMap entry will be GC'd automatically// Regular Map prevents GCconst regularCache = new Map();let user2 = {id: 2, name: 'Bob'};regularCache.set(user2, {data: 'value'});user2 = null;// Object is still in Map, won't be GC'd// Must manually: regularCache.delete(user2)
Key Points: JavaScript uses automatic garbage collection.
Modern engines use mark-and-sweep algorithm. V8 uses generational GC (young/old).
Objects are GC'd when unreachable from GC roots. Circular references are handled
correctly. Use WeakMap/WeakSet for cache that allows GC. Monitor memory with performance.memory.
2. Memory Leak Prevention and Detection
Common Memory Leak Patterns
Pattern
Cause
Solution
Global variables
Accidental globals, never released
Use strict mode, const/let, proper scoping
Forgotten timers
setInterval/setTimeout not cleared
clearInterval, clearTimeout in cleanup
Event listeners
Listeners not removed
removeEventListener in cleanup
Closures
Large data captured in closure
Limit scope, nullify references
Detached DOM
DOM elements removed but referenced
Remove JS references to deleted elements
Cache without limit
Unbounded cache growth
Use WeakMap, LRU cache, size limits
Console.log references
Objects logged kept in memory
Remove logs in production
Detection Tools and Techniques
Tool
Purpose
How to Use
Chrome DevTools Memory
Heap snapshots, allocation timeline
Performance > Memory, take snapshots
Heap Snapshot Comparison
Find objects not being freed
Take 2+ snapshots, compare
Allocation Timeline
Track allocations over time
Record timeline, analyze patterns
performance.memory
Monitor heap size programmatically
Check usedJSHeapSize periodically
Node.js --inspect
Debug Node.js memory
node --inspect app.js
process.memoryUsage()
Node.js memory stats
Log heapUsed, rss
Prevention Best Practices
Practice
Description
Benefit
Use strict mode
Prevent accidental globals
Catches undeclared variables
Cleanup in lifecycle
Remove listeners, clear timers
Proper resource management
WeakMap/WeakSet
Allow garbage collection
Automatic cache cleanup
Limit closure scope
Don't capture unnecessary data
Reduce memory footprint
Nullify references
Set to null when done
Make objects eligible for GC
Use object pools
Reuse objects instead of creating
Reduce allocation pressure
Example: Common memory leak patterns
// LEAK 1: Accidental globalfunction createLeak1() { // Missing 'const', creates global leakedVariable = 'This is global'; // BAD}createLeak1();console.log(window.leakedVariable); // Accessible globally// FIX: Use strict mode and proper declarations'use strict';function noLeak1() { const properVariable = 'This is local'; // GOOD}// LEAK 2: Forgotten timerfunction createLeak2() { const largeData = new Array(1000000).fill('data'); setInterval(() => { console.log(largeData.length); // Keeps largeData in memory }, 1000); // BAD: Never cleared}createLeak2();// FIX: Clear timerfunction noLeak2() { const largeData = new Array(1000000).fill('data'); const timer = setInterval(() => { console.log(largeData.length); }, 1000); // Clear when done setTimeout(() => clearInterval(timer), 10000); // GOOD}// LEAK 3: Event listener not removedclass ComponentWithLeak { constructor() { this.data = new Array(1000000).fill('data'); // BAD: Listener keeps component in memory document.addEventListener('click', this.handleClick.bind(this)); } handleClick() { console.log(this.data.length); } // No cleanup method}// FIX: Remove listenerclass ComponentWithoutLeak { constructor() { this.data = new Array(1000000).fill('data'); this.boundHandleClick = this.handleClick.bind(this); document.addEventListener('click', this.boundHandleClick); } handleClick() { console.log(this.data.length); } destroy() { // GOOD: Remove listener document.removeEventListener('click', this.boundHandleClick); this.data = null; }}// LEAK 4: Detached DOM nodeslet detachedNodes = [];function createLeak4() { const div = document.createElement('div'); div.innerHTML = '<p>Content</p>'; document.body.appendChild(div); // Store reference detachedNodes.push(div); // Remove from DOM but still referenced document.body.removeChild(div); // BAD: Still in detachedNodes array}// FIX: Remove all referencesfunction noLeak4() { const div = document.createElement('div'); div.innerHTML = '<p>Content</p>'; document.body.appendChild(div); // When removing from DOM, clear references document.body.removeChild(div); div = null; // GOOD}// LEAK 5: Closures capturing large datafunction createLeak5() { const hugeArray = new Array(1000000).fill('data'); return function() { // This closure captures hugeArray even if not used console.log('Function called'); };}const fn = createLeak5(); // hugeArray kept in memory// FIX: Limit closure scopefunction noLeak5() { const hugeArray = new Array(1000000).fill('data'); const length = hugeArray.length; // Extract only what's needed return function() { console.log('Array had', length, 'elements'); // GOOD // hugeArray is not captured };}// LEAK 6: Unbounded cacheconst cache = {};function addToCache(key, value) { cache[key] = value; // BAD: Grows forever}// FIX: Use WeakMap or size limitconst properCache = new WeakMap();// Or LRU cache with size limitclass LRUCache { constructor(maxSize = 100) { this.maxSize = maxSize; this.cache = new Map(); } set(key, value) { // Remove oldest if at capacity if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(key, value); } get(key) { const value = this.cache.get(key); if (value !== undefined) { // Move to end (most recently used) this.cache.delete(key); this.cache.set(key, value); } return value; }}
Example: Memory leak detection
// Automated leak detectionclass LeakDetector { constructor(threshold = 50 * 1024 * 1024) { // 50MB this.threshold = threshold; this.measurements = []; this.warnings = 0; } measure() { if (!performance.memory) { console.warn('performance.memory not available'); return; } const current = performance.memory.usedJSHeapSize; this.measurements.push({ timestamp: Date.now(), heapSize: current }); // Keep last 100 measurements if (this.measurements.length > 100) { this.measurements.shift(); } this.analyze(); } analyze() { if (this.measurements.length < 10) return; const first = this.measurements[0]; const last = this.measurements[this.measurements.length - 1]; const growth = last.heapSize - first.heapSize; const duration = last.timestamp - first.timestamp; const growthRate = growth / (duration / 1000); // bytes per second // Check if consistently growing if (growth > this.threshold) { this.warnings++; console.warn('Memory leak suspected!'); console.log('Growth:', Math.round(growth / 1048576), 'MB'); console.log('Duration:', Math.round(duration / 1000), 'seconds'); console.log('Rate:', Math.round(growthRate / 1024), 'KB/s'); this.captureSnapshot(); } } captureSnapshot() { console.log('Capturing heap snapshot...'); // In real implementation, take heap snapshot via DevTools Protocol } startMonitoring(interval = 5000) { this.timer = setInterval(() => this.measure(), interval); } stopMonitoring() { clearInterval(this.timer); }}const detector = new LeakDetector();detector.startMonitoring(5000);// Leak test helperasync function testForLeaks(fn, iterations = 10) { if (!performance.memory) { console.log('Memory profiling not available'); return; } // Force GC if available if (global.gc) global.gc(); const before = performance.memory.usedJSHeapSize; for (let i = 0; i < iterations; i++) { fn(); } // Force GC if available if (global.gc) global.gc(); await new Promise(resolve => setTimeout(resolve, 1000)); const after = performance.memory.usedJSHeapSize; const growth = after - before; console.log('Memory before:', Math.round(before / 1024), 'KB'); console.log('Memory after:', Math.round(after / 1024), 'KB'); console.log('Growth:', Math.round(growth / 1024), 'KB'); if (growth > 1024 * 1024) { // 1MB console.warn('Possible memory leak detected'); } else { console.log('No significant memory growth'); }}// Test a function for leaksawait testForLeaks(() => { // Function to test const data = new Array(10000).fill('test'); // Should be cleaned up after each iteration});
Key Points: Common leaks: globals, forgotten timers, event listeners, closures, detached DOM.
Use Chrome DevTools Memory profiler for detection. Take heap snapshots and
compare. Always clean up: removeEventListener, clearInterval. Use
WeakMap/WeakSet for auto-cleanup caches. Implement proper lifecycle cleanup.
Test for leaks by running operations repeatedly.
3. Performance Optimization Strategies
Rendering Performance
Strategy
Technique
Impact
Minimize DOM access
Cache DOM references, batch reads/writes
Reduce layout thrashing
Avoid layout thrashing
Separate read and write operations
Prevent forced reflows
Use DocumentFragment
Build DOM off-document, append once
Single reflow instead of many
Virtual scrolling
Render only visible items
Handle large lists efficiently
Debounce/throttle
Limit expensive operations frequency
Reduce CPU usage
requestAnimationFrame
Sync visual changes with refresh rate
Smooth animations, no jank
JavaScript Performance
Strategy
Technique
Impact
Avoid premature optimization
Profile first, optimize bottlenecks
Focus effort where it matters
Use efficient algorithms
Choose O(n log n) over O(n²)
Better scalability
Minimize object creation
Reuse objects, object pools
Reduce GC pressure
Memoization
Cache expensive computation results
Avoid redundant calculations
Lazy evaluation
Defer work until needed
Faster initial load
Web Workers
Move heavy computation off main thread
Keep UI responsive
Network Performance
Strategy
Technique
Impact
Minimize bundle size
Code splitting, tree shaking
Faster download and parse
Compress assets
Gzip, Brotli compression
Smaller transfer size
Cache effectively
HTTP caching, service workers
Avoid re-downloading
CDN usage
Serve assets from edge locations
Reduced latency
Resource hints
preload, prefetch, dns-prefetch
Anticipate needed resources
HTTP/2
Multiplexing, server push
Better resource loading
Example: Avoiding layout thrashing
// BAD: Layout thrashing (alternating read/write)function layoutThrashing() { const elements = document.querySelectorAll('.item'); elements.forEach(el => { // Read (causes layout) const height = el.offsetHeight; // Write (invalidates layout) el.style.height = height + 10 + 'px'; // Next read will force reflow });}// GOOD: Batch reads, then writesfunction noLayoutThrashing() { const elements = document.querySelectorAll('.item'); // Phase 1: Read all const heights = Array.from(elements).map(el => el.offsetHeight); // Phase 2: Write all elements.forEach((el, i) => { el.style.height = heights[i] + 10 + 'px'; });}// Use FastDOM library for automatic batchingfastdom.measure(() => { const height = element.offsetHeight; fastdom.mutate(() => { element.style.height = height + 10 + 'px'; });});// Efficient DOM manipulation// BAD: Multiple reflowsfunction inefficientDOMUpdate() { for (let i = 0; i < 100; i++) { const div = document.createElement('div'); div.textContent = `Item ${i}`; document.body.appendChild(div); // Reflow on each append }}// GOOD: Single reflow with DocumentFragmentfunction efficientDOMUpdate() { const fragment = document.createDocumentFragment(); for (let i = 0; i < 100; i++) { const div = document.createElement('div'); div.textContent = `Item ${i}`; fragment.appendChild(div); // No reflow } document.body.appendChild(fragment); // Single reflow}// GOOD: Clone and replacefunction efficientDOMReplace() { const container = document.getElementById('container'); const clone = container.cloneNode(false); // Empty clone // Build new content for (let i = 0; i < 100; i++) { const div = document.createElement('div'); div.textContent = `Item ${i}`; clone.appendChild(div); } // Replace in one operation container.parentNode.replaceChild(clone, container);}
Example: Debounce and throttle
// Debounce: Wait for quiet periodfunction debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); };}// Usage: Search as user types (wait for pause)const searchInput = document.getElementById('search');const debouncedSearch = debounce((query) => { console.log('Searching for:', query); // Expensive API call fetch(`/api/search?q=${query}`);}, 300);searchInput.addEventListener('input', (e) => { debouncedSearch(e.target.value);});// Throttle: Limit execution frequencyfunction throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => { inThrottle = false; }, limit); } };}// Usage: Scroll event (execute at most once per 100ms)const throttledScroll = throttle(() => { console.log('Scroll position:', window.scrollY); // Update UI based on scroll}, 100);window.addEventListener('scroll', throttledScroll);// requestAnimationFrame throttle (better for visuals)function rafThrottle(func) { let rafId = null; return function(...args) { if (rafId === null) { rafId = requestAnimationFrame(() => { func.apply(this, args); rafId = null; }); } };}const rafThrottledScroll = rafThrottle(() => { // Smooth visual updates updateScrollIndicator();});window.addEventListener('scroll', rafThrottledScroll);// Debounce with immediate optionfunction debounceImmediate(func, delay) { let timeoutId; return function(...args) { const callNow = !timeoutId; clearTimeout(timeoutId); timeoutId = setTimeout(() => { timeoutId = null; }, delay); if (callNow) { func.apply(this, args); } };}// Execute immediately, then debounceconst immediateDebounce = debounceImmediate((e) => { console.log('Button clicked');}, 1000);button.addEventListener('click', immediateDebounce);
Example: Memoization and caching
// Simple memoizationfunction memoize(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn.apply(this, args); cache.set(key, result); return result; };}// Expensive computationfunction fibonacci(n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2);}const memoizedFib = memoize(fibonacci);console.time('first');console.log(memoizedFib(40)); // Slow first timeconsole.timeEnd('first');console.time('second');console.log(memoizedFib(40)); // Instant from cacheconsole.timeEnd('second');// Memoization with TTLfunction memoizeWithTTL(fn, ttl = 60000) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); const cached = cache.get(key); if (cached && Date.now() - cached.timestamp < ttl) { return cached.value; } const result = fn.apply(this, args); cache.set(key, { value: result, timestamp: Date.now() }); return result; };}const cachedFetch = memoizeWithTTL(async (url) => { const response = await fetch(url); return response.json();}, 5000); // 5 second cache// LRU Cache for bounded memoryclass LRUCache { constructor(maxSize = 100) { this.maxSize = maxSize; this.cache = new Map(); } get(key) { if (!this.cache.has(key)) return undefined; const value = this.cache.get(key); // Move to end (most recent) this.cache.delete(key); this.cache.set(key, value); return value; } set(key, value) { // Delete if exists (to reinsert at end) if (this.cache.has(key)) { this.cache.delete(key); } // Evict oldest if at capacity if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(key, value); }}// Memoize with LRUfunction memoizeLRU(fn, maxSize = 100) { const cache = new LRUCache(maxSize); return function(...args) { const key = JSON.stringify(args); let result = cache.get(key); if (result === undefined) { result = fn.apply(this, args); cache.set(key, result); } return result; };}// Object pool for reusing objectsclass ObjectPool { constructor(factory, reset, initialSize = 10) { this.factory = factory; this.reset = reset; this.available = []; for (let i = 0; i < initialSize; i++) { this.available.push(factory()); } } acquire() { return this.available.length > 0 ? this.available.pop() : this.factory(); } release(obj) { this.reset(obj); this.available.push(obj); }}// Usageconst vectorPool = new ObjectPool( () => ({x: 0, y: 0, z: 0}), (v) => { v.x = 0; v.y = 0; v.z = 0; });function processVectors() { const v1 = vectorPool.acquire(); const v2 = vectorPool.acquire(); // Use vectors v1.x = 10; v2.y = 20; // Release back to pool vectorPool.release(v1); vectorPool.release(v2);}
Key Points: Avoid layout thrashing by batching DOM reads/writes.
Use debounce for search, throttle for scroll/resize.
requestAnimationFrame
for smooth animations. Memoize expensive computations. Use LRU cache for bounded
memory. Object pools reduce GC pressure. Profile before optimizing - measure, don't guess.
4. Profiling and Performance Monitoring
Performance API Methods
Method
Purpose
Returns
performance.now()
High-resolution timestamp
DOMHighResTimeStamp (microsecond precision)
performance.mark()
Create named timestamp
undefined
performance.measure()
Measure between two marks
Performance entry
performance.getEntries()
Get all performance entries
Array of PerformanceEntry
performance.getEntriesByType()
Filter entries by type
Array of PerformanceEntry
performance.getEntriesByName()
Get entries by name
Array of PerformanceEntry
performance.clearMarks()
Clear marks
undefined
performance.clearMeasures()
Clear measures
undefined
Performance Entry Types
Type
Description
Key Properties
navigation
Page navigation timing
domContentLoadedEventEnd, loadEventEnd
resource
Resource loading (scripts, images)
duration, transferSize, initiatorType
mark
Custom timestamp markers
startTime, name
measure
Time between marks
duration, startTime
paint
Paint timing (FCP, FP)
startTime
longtask
Tasks > 50ms (Long Task API)
duration, startTime
Core Web Vitals
Metric
Measures
Good Threshold
LCP (Largest Contentful Paint)
Loading performance
< 2.5 seconds
FID (First Input Delay)
Interactivity
< 100 milliseconds
CLS (Cumulative Layout Shift)
Visual stability
< 0.1
FCP (First Contentful Paint)
Perceived load speed
< 1.8 seconds
TTFB (Time to First Byte)
Server response time
< 600 milliseconds
TTI (Time to Interactive)
When page becomes interactive
< 3.8 seconds
Chrome DevTools Profiling
Tool
Purpose
Usage
Performance tab
Record runtime performance
Identify bottlenecks, long tasks
Memory tab
Heap snapshots, allocation timeline
Find memory leaks
Lighthouse
Automated performance audit
Get recommendations
Coverage tab
Find unused JavaScript/CSS
Reduce bundle size
Network tab
Monitor requests and timing
Optimize loading
Example: Performance measurement
// Basic timing with performance.now()const start = performance.now();// Some operationfor (let i = 0; i < 1000000; i++) { // work}const end = performance.now();console.log(`Operation took ${end - start} milliseconds`);// User Timing API with marks and measuresperformance.mark('fetch-start');fetch('/api/data') .then(response => response.json()) .then(data => { performance.mark('fetch-end'); // Measure between marks performance.measure('fetch-duration', 'fetch-start', 'fetch-end'); const measure = performance.getEntriesByName('fetch-duration')[0]; console.log(`Fetch took ${measure.duration}ms`); });// Measure component renderfunction measureRender(component, props) { performance.mark(`${component}-render-start`); const result = renderComponent(component, props); performance.mark(`${component}-render-end`); performance.measure( `${component}-render`, `${component}-render-start`, `${component}-render-end` ); return result;}// Get all measuresconst measures = performance.getEntriesByType('measure');measures.forEach(measure => { console.log(`${measure.name}: ${measure.duration.toFixed(2)}ms`);});// Clear marks and measuresperformance.clearMarks();performance.clearMeasures();// Navigation timingconst navTiming = performance.getEntriesByType('navigation')[0];console.log('Page Load Metrics:');console.log('DNS lookup:', navTiming.domainLookupEnd - navTiming.domainLookupStart, 'ms');console.log('TCP connection:', navTiming.connectEnd - navTiming.connectStart, 'ms');console.log('Request time:', navTiming.responseStart - navTiming.requestStart, 'ms');console.log('Response time:', navTiming.responseEnd - navTiming.responseStart, 'ms');console.log('DOM processing:', navTiming.domComplete - navTiming.domLoading, 'ms');console.log('Load event:', navTiming.loadEventEnd - navTiming.loadEventStart, 'ms');// Resource timingconst resources = performance.getEntriesByType('resource');resources.forEach(resource => { console.log(resource.name); console.log(' Duration:', resource.duration.toFixed(2), 'ms'); console.log(' Transfer size:', resource.transferSize, 'bytes'); console.log(' Type:', resource.initiatorType);});// Find slow resourcesconst slowResources = resources.filter(r => r.duration > 1000);console.log('Resources > 1s:', slowResources.length);
Example: Performance monitoring class
// Comprehensive performance monitorclass PerformanceMonitor { constructor() { this.metrics = new Map(); this.observers = []; this.initObservers(); } initObservers() { // Observe long tasks (> 50ms) if ('PerformanceLongTaskTiming' in window) { const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.warn('Long task detected:', entry.duration, 'ms'); this.recordMetric('long-task', entry.duration); } }); observer.observe({entryTypes: ['longtask']}); this.observers.push(observer); } // Observe layout shifts (CLS) if ('LayoutShift' in window) { const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { this.recordMetric('layout-shift', entry.value); } } }); observer.observe({entryTypes: ['layout-shift']}); this.observers.push(observer); } // Observe largest contentful paint (LCP) if ('LargestContentfulPaint' in window) { const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; this.recordMetric('lcp', lastEntry.startTime); console.log('LCP:', lastEntry.startTime, 'ms'); }); observer.observe({entryTypes: ['largest-contentful-paint']}); this.observers.push(observer); } // Observe first input delay (FID) if ('PerformanceEventTiming' in window) { const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { const fid = entry.processingStart - entry.startTime; this.recordMetric('fid', fid); console.log('FID:', fid, 'ms'); } }); observer.observe({entryTypes: ['first-input']}); this.observers.push(observer); } } recordMetric(name, value) { if (!this.metrics.has(name)) { this.metrics.set(name, []); } this.metrics.get(name).push({ value, timestamp: Date.now() }); } measureFunction(name, fn) { const start = performance.now(); try { const result = fn(); const duration = performance.now() - start; this.recordMetric(name, duration); if (duration > 50) { console.warn(`${name} took ${duration.toFixed(2)}ms`); } return result; } catch (error) { const duration = performance.now() - start; this.recordMetric(name, duration); throw error; } } async measureAsync(name, fn) { const start = performance.now(); try { const result = await fn(); const duration = performance.now() - start; this.recordMetric(name, duration); return result; } catch (error) { const duration = performance.now() - start; this.recordMetric(name, duration); throw error; } } getReport() { const report = {}; for (const [name, values] of this.metrics) { const durations = values.map(v => v.value); report[name] = { count: durations.length, min: Math.min(...durations), max: Math.max(...durations), avg: durations.reduce((a, b) => a + b, 0) / durations.length, p50: this.percentile(durations, 0.5), p95: this.percentile(durations, 0.95), p99: this.percentile(durations, 0.99) }; } return report; } percentile(values, p) { const sorted = values.slice().sort((a, b) => a - b); const index = Math.ceil(sorted.length * p) - 1; return sorted[index]; } sendToAnalytics() { const report = this.getReport(); // Send to analytics service navigator.sendBeacon('/api/performance', JSON.stringify(report)); } cleanup() { this.observers.forEach(observer => observer.disconnect()); this.observers = []; }}// Usageconst monitor = new PerformanceMonitor();// Measure functionmonitor.measureFunction('processData', () => { // Some work return processData();});// Measure async functionawait monitor.measureAsync('fetchData', async () => { return await fetch('/api/data').then(r => r.json());});// Get performance reportconst report = monitor.getReport();console.log('Performance Report:', report);// Send to analytics on page unloadwindow.addEventListener('beforeunload', () => { monitor.sendToAnalytics();});
Example: Core Web Vitals measurement
// Measure Core Web Vitalsclass WebVitals { constructor() { this.vitals = {}; } measureLCP() { if (!('LargestContentfulPaint' in window)) return; const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; this.vitals.lcp = lastEntry.startTime; // Report this.reportVital('LCP', lastEntry.startTime); }); observer.observe({entryTypes: ['largest-contentful-paint']}); } measureFID() { if (!('PerformanceEventTiming' in window)) return; const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { const fid = entry.processingStart - entry.startTime; this.vitals.fid = fid; this.reportVital('FID', fid); } }); observer.observe({entryTypes: ['first-input']}); } measureCLS() { if (!('LayoutShift' in window)) return; let clsScore = 0; const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsScore += entry.value; } } this.vitals.cls = clsScore; }); observer.observe({entryTypes: ['layout-shift']}); // Report on page hide document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { this.reportVital('CLS', clsScore); } }); } measureFCP() { if (!('PerformancePaintTiming' in window)) return; const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.name === 'first-contentful-paint') { this.vitals.fcp = entry.startTime; this.reportVital('FCP', entry.startTime); } } }); observer.observe({entryTypes: ['paint']}); } measureTTFB() { const navTiming = performance.getEntriesByType('navigation')[0]; if (navTiming) { const ttfb = navTiming.responseStart - navTiming.requestStart; this.vitals.ttfb = ttfb; this.reportVital('TTFB', ttfb); } } reportVital(name, value) { console.log(`${name}:`, value.toFixed(2), 'ms'); // Check against thresholds const thresholds = { LCP: {good: 2500, needsImprovement: 4000}, FID: {good: 100, needsImprovement: 300}, CLS: {good: 0.1, needsImprovement: 0.25}, FCP: {good: 1800, needsImprovement: 3000}, TTFB: {good: 600, needsImprovement: 1500} }; const threshold = thresholds[name]; if (threshold) { let rating; if (value <= threshold.good) { rating = 'good'; } else if (value <= threshold.needsImprovement) { rating = 'needs improvement'; } else { rating = 'poor'; } console.log(`${name} rating: ${rating}`); } // Send to analytics this.sendToAnalytics(name, value); } sendToAnalytics(name, value) { // Send to analytics service if (navigator.sendBeacon) { navigator.sendBeacon('/api/vitals', JSON.stringify({ metric: name, value: value, timestamp: Date.now(), url: window.location.href })); } } measureAll() { this.measureLCP(); this.measureFID(); this.measureCLS(); this.measureFCP(); this.measureTTFB(); }}// Initializeconst webVitals = new WebVitals();webVitals.measureAll();// Alternative: Use web-vitals libraryimport {getLCP, getFID, getCLS, getFCP, getTTFB} from 'web-vitals';getLCP(console.log);getFID(console.log);getCLS(console.log);getFCP(console.log);getTTFB(console.log);
Key Points: Use performance.now() for precise timing.
performance.mark()
and performance.measure() for custom metrics. Monitor Core Web
Vitals:
LCP, FID, CLS. Use PerformanceObserver to track long tasks, layout shifts. Send metrics to
analytics
with navigator.sendBeacon(). Profile with Chrome DevTools Performance tab.
Key Points: Use import() for dynamic code
splitting.
Split by route, component, or feature. Use IntersectionObserver for lazy loading images/components.
Prefetch likely-needed modules on hover/idle. Native loading="lazy" for images. Tree shaking
removes
unused code. Analyze bundles with webpack-bundle-analyzer. Cache loaded modules to avoid re-loading.
6. Optimization for V8 Engine
V8 Optimization Concepts
Concept
Description
Impact
Hidden Classes
Object shape/structure
Property access performance
Inline Caching
Cache property lookup locations
Faster repeated access
JIT Compilation
Compile hot code to machine code
Execution speed
Optimization Tiers
Ignition (interpreter), TurboFan (optimizer)
Progressive optimization
Deoptimization
Fall back to interpreter on type change
Performance penalty
Optimization-Friendly Patterns
Pattern
Why It's Fast
Example
Monomorphic functions
Same types always, optimizer-friendly
Always pass same object shape
Fixed object structure
Stable hidden class
Initialize all properties in constructor
Array of same type
Optimized element access
All numbers or all objects
Small functions
Can be inlined
Single responsibility
Avoid delete
Maintains hidden class
Set to null/undefined instead
Predictable types
Reduces deoptimization
Consistent return types
Common Deoptimization Triggers
Trigger
Why It's Slow
Solution
Dynamic property addition
Changes hidden class
Initialize all properties upfront
delete operator
Breaks hidden class optimization
Set to null/undefined
Polymorphic functions
Multiple code paths
Use monomorphic versions
Sparse arrays
Dictionary mode storage
Use dense arrays
Mixed array types
Generic element storage
Homogeneous arrays
try-catch in hot path
Prevents optimization
Isolate try-catch to helper function
Example: Hidden classes and object shape
// BAD: Dynamic property addition (different hidden classes)function createPoint1() { const point = {}; point.x = 10; // Hidden class transition 1 point.y = 20; // Hidden class transition 2 return point;}const p1 = createPoint1();const p2 = createPoint1();p2.z = 30; // p2 now has different hidden class than p1// GOOD: Initialize all properties (same hidden class)function createPoint2() { return { x: 10, // All properties at once y: 20, z: 0 };}const p3 = createPoint2();const p4 = createPoint2();// p3 and p4 share the same hidden class// GOOD: Constructor with fixed structureclass Point { constructor(x, y) { this.x = x; // Always same order this.y = y; // Always same properties }}// BAD: Inconsistent object structurefunction createUser(name, age, email) { const user = {name, age}; // Sometimes add email, sometimes don't if (email) { user.email = email; // Different hidden class! } return user;}// GOOD: Always same structurefunction createUser2(name, age, email = null) { return { name, age, email // Always present, even if null };}// BAD: Delete property (kills optimization)const obj = {a: 1, b: 2, c: 3};delete obj.b; // BAD: Changes hidden class// GOOD: Set to undefinedconst obj2 = {a: 1, b: 2, c: 3};obj2.b = undefined; // GOOD: Maintains hidden class// Monomorphic vs Polymorphic functions// BAD: Polymorphic (accepts different shapes)function processData(data) { if (Array.isArray(data)) { return data.map(x => x * 2); } else if (typeof data === 'object') { return Object.values(data).map(x => x * 2); } return data * 2;}// GOOD: Monomorphic (always same type)function processArray(arr) { return arr.map(x => x * 2);}function processObject(obj) { return Object.values(obj).map(x => x * 2);}function processNumber(num) { return num * 2;}