JavaScript Memory Management and Performance
1. Garbage Collection and Memory Lifecycle
JavaScript Memory Lifecycle
| Phase | Description | Automatic/Manual |
|---|---|---|
| 1. Allocation | Memory allocated for variables, objects | Automatic |
| 2. Usage | Read/write operations on allocated memory | Manual (developer code) |
| 3. Deallocation | Release unused memory back to system | Automatic (GC) |
Garbage Collection Algorithms
| Algorithm | Strategy | Usage |
|---|---|---|
| Reference Counting (legacy) | Track number of references to object | Old browsers (circular ref issues) |
| Mark-and-Sweep | Mark reachable objects, sweep unreachable | Modern browsers (default) |
| Generational GC | Separate young/old objects | V8 (Chrome, Node.js) |
| Incremental GC | Split GC work into small chunks | Reduce pause times |
| Concurrent GC | Run GC in parallel with app | V8 Orinoco |
V8 Heap Structure
| Space | Purpose | GC Strategy |
|---|---|---|
| New Space (Young Gen) | Recently allocated objects | Scavenge (fast, frequent) |
| Old Space (Old Gen) | Objects survived multiple GC cycles | Mark-Sweep-Compact (slower, less frequent) |
| Large Object Space | Objects > 1MB | Never moved |
| Code Space | Compiled code | Mark-Sweep |
| Map Space | Hidden classes (object shapes) | Mark-Sweep |
Reachability and GC Roots
| GC Root | Description | Example |
|---|---|---|
| Global object | window (browser), global (Node.js) | window.myGlobal |
| Local variables | Variables in current execution context | Function parameters, local vars |
| Closures | Variables captured by closures | Outer scope variables |
| Active stack | Call stack variables | Variables in executing functions |
| DOM elements | Elements attached to document | document.body references |
Example: Understanding garbage collection
// Objects become eligible for GC when unreachable
function 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 GC
result = 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 alive
function 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 variables
fn = null; // Now largeData can be GC'd
// Global variables never collected
window.globalData = new Array(1000000); // Stays until page unload
// WeakMap/WeakSet allow GC
const 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 elsewhere
user = null;
// The WeakMap entry will be GC'd automatically
// Regular Map prevents GC
const 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)
Example: Monitoring memory usage
// Check memory usage (Chrome only)
if (performance.memory) {
console.log('Memory usage:');
console.log('Used JS Heap:',
Math.round(performance.memory.usedJSHeapSize / 1048576), 'MB');
console.log('Total JS Heap:',
Math.round(performance.memory.totalJSHeapSize / 1048576), 'MB');
console.log('JS Heap Limit:',
Math.round(performance.memory.jsHeapSizeLimit / 1048576), 'MB');
}
// Memory monitor
class MemoryMonitor {
constructor(interval = 5000) {
this.interval = interval;
this.measurements = [];
}
start() {
this.timer = setInterval(() => {
if (performance.memory) {
const measurement = {
timestamp: Date.now(),
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit
};
this.measurements.push(measurement);
this.checkForLeaks();
}
}, this.interval);
}
stop() {
clearInterval(this.timer);
}
checkForLeaks() {
if (this.measurements.length < 10) return;
// Check if memory is consistently growing
const recent = this.measurements.slice(-10);
const growthRate = recent.map((m, i) => {
if (i === 0) return 0;
return m.used - recent[i - 1].used;
});
const avgGrowth = growthRate.reduce((a, b) => a + b, 0) / growthRate.length;
if (avgGrowth > 1048576) { // 1MB average growth
console.warn('Potential memory leak detected');
console.log('Average growth:', Math.round(avgGrowth / 1024), 'KB/measurement');
}
}
getReport() {
if (this.measurements.length === 0) return null;
const first = this.measurements[0];
const last = this.measurements[this.measurements.length - 1];
return {
duration: last.timestamp - first.timestamp,
initialMemory: Math.round(first.used / 1048576),
currentMemory: Math.round(last.used / 1048576),
growth: Math.round((last.used - first.used) / 1048576),
measurements: this.measurements.length
};
}
}
const monitor = new MemoryMonitor(5000);
monitor.start();
// Later...
const report = monitor.getReport();
console.log('Memory Report:', report);
monitor.stop();
// Force garbage collection (Node.js with --expose-gc flag)
if (global.gc) {
console.log('Before GC:', process.memoryUsage().heapUsed);
global.gc();
console.log('After GC:', process.memoryUsage().heapUsed);
}
// Node.js memory usage
function logMemoryUsage() {
const usage = process.memoryUsage();
console.log('Memory Usage:');
console.log('RSS:', Math.round(usage.rss / 1048576), 'MB');
console.log('Heap Total:', Math.round(usage.heapTotal / 1048576), 'MB');
console.log('Heap Used:', Math.round(usage.heapUsed / 1048576), 'MB');
console.log('External:', Math.round(usage.external / 1048576), 'MB');
}
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 global
function 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 timer
function createLeak2() {
const largeData = new Array(1000000).fill('data');
setInterval(() => {
console.log(largeData.length); // Keeps largeData in memory
}, 1000); // BAD: Never cleared
}
createLeak2();
// FIX: Clear timer
function 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 removed
class 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 listener
class 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 nodes
let 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 references
function 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 data
function 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 scope
function 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 cache
const cache = {};
function addToCache(key, value) {
cache[key] = value; // BAD: Grows forever
}
// FIX: Use WeakMap or size limit
const properCache = new WeakMap();
// Or LRU cache with size limit
class 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 detection
class 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 helper
async 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 leaks
await testForLeaks(() => {
// Function to test
const data = new Array(10000).fill('test');
// Should be cleaned up after each iteration
});
Example: Proper cleanup patterns
// React-style lifecycle with cleanup
class Component {
constructor() {
this.timers = [];
this.listeners = [];
this.subscriptions = [];
}
mount() {
// Add timer
const timer = setInterval(() => {
this.update();
}, 1000);
this.timers.push(timer);
// Add event listener
const handler = this.handleClick.bind(this);
document.addEventListener('click', handler);
this.listeners.push({element: document, event: 'click', handler});
// Add subscription
const sub = eventBus.subscribe('data', this.handleData.bind(this));
this.subscriptions.push(sub);
}
unmount() {
// Clean up timers
this.timers.forEach(timer => clearInterval(timer));
this.timers = [];
// Remove event listeners
this.listeners.forEach(({element, event, handler}) => {
element.removeEventListener(event, handler);
});
this.listeners = [];
// Unsubscribe
this.subscriptions.forEach(sub => sub.unsubscribe());
this.subscriptions = [];
// Nullify large data
this.data = null;
}
handleClick(e) {
console.log('Clicked');
}
handleData(data) {
this.data = data;
}
update() {
console.log('Update');
}
}
// Cleanup helper
class CleanupManager {
constructor() {
this.cleanupFns = [];
}
register(cleanupFn) {
this.cleanupFns.push(cleanupFn);
}
cleanup() {
this.cleanupFns.forEach(fn => {
try {
fn();
} catch (error) {
console.error('Cleanup error:', error);
}
});
this.cleanupFns = [];
}
}
// Usage
const manager = new CleanupManager();
// Register cleanup for timer
const timer = setInterval(() => {}, 1000);
manager.register(() => clearInterval(timer));
// Register cleanup for listener
const handler = () => {};
document.addEventListener('click', handler);
manager.register(() => document.removeEventListener('click', handler));
// Later: cleanup all
manager.cleanup();
// Automatic cleanup with Symbol.dispose (TC39 proposal)
class ResourceWithCleanup {
constructor() {
this.resource = acquireResource();
}
[Symbol.dispose]() {
this.resource.close();
this.resource = null;
}
}
// Will auto-cleanup when leaving scope
{
using resource = new ResourceWithCleanup();
// Use resource
} // Automatic cleanup here
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 writes
function 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 batching
fastdom.measure(() => {
const height = element.offsetHeight;
fastdom.mutate(() => {
element.style.height = height + 10 + 'px';
});
});
// Efficient DOM manipulation
// BAD: Multiple reflows
function 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 DocumentFragment
function 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 replace
function 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 period
function 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 frequency
function 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 option
function 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 debounce
const immediateDebounce = debounceImmediate((e) => {
console.log('Button clicked');
}, 1000);
button.addEventListener('click', immediateDebounce);
Example: Memoization and caching
// Simple memoization
function 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 computation
function 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 time
console.timeEnd('first');
console.time('second');
console.log(memoizedFib(40)); // Instant from cache
console.timeEnd('second');
// Memoization with TTL
function 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 memory
class 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 LRU
function 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 objects
class 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);
}
}
// Usage
const 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 operation
for (let i = 0; i < 1000000; i++) {
// work
}
const end = performance.now();
console.log(`Operation took ${end - start} milliseconds`);
// User Timing API with marks and measures
performance.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 render
function 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 measures
const measures = performance.getEntriesByType('measure');
measures.forEach(measure => {
console.log(`${measure.name}: ${measure.duration.toFixed(2)}ms`);
});
// Clear marks and measures
performance.clearMarks();
performance.clearMeasures();
// Navigation timing
const 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 timing
const 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 resources
const slowResources = resources.filter(r => r.duration > 1000);
console.log('Resources > 1s:', slowResources.length);
Example: Performance monitoring class
// Comprehensive performance monitor
class 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 = [];
}
}
// Usage
const monitor = new PerformanceMonitor();
// Measure function
monitor.measureFunction('processData', () => {
// Some work
return processData();
});
// Measure async function
await monitor.measureAsync('fetchData', async () => {
return await fetch('/api/data').then(r => r.json());
});
// Get performance report
const report = monitor.getReport();
console.log('Performance Report:', report);
// Send to analytics on page unload
window.addEventListener('beforeunload', () => {
monitor.sendToAnalytics();
});
Example: Core Web Vitals measurement
// Measure Core Web Vitals
class 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();
}
}
// Initialize
const webVitals = new WebVitals();
webVitals.measureAll();
// Alternative: Use web-vitals library
import {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.
5. Code Splitting and Lazy Loading
Code Splitting Strategies
| Strategy | When to Use | Implementation |
|---|---|---|
| Route-based | Different pages/routes | Split by route, load on navigation |
| Component-based | Large/heavy components | Dynamic import, lazy load |
| Vendor splitting | Third-party libraries | Separate vendor bundle, cache long-term |
| Feature-based | Optional features | Load only when feature used |
| Conditional | Environment-specific code | Load based on conditions |
Dynamic Import Syntax
| Syntax | Returns | Usage |
|---|---|---|
| import() | Promise<Module> | Dynamic module loading |
| await import() | Module object | Top-level await or async function |
| import().then() | Promise chain | Handle module in .then() |
Lazy Loading Techniques
| Technique | Target | Trigger |
|---|---|---|
| Intersection Observer | Images, components | When entering viewport |
| On interaction | Modal, tooltip, menu | Click, hover, focus |
| On route change | Page components | Navigation event |
| Prefetching | Likely-needed code | Idle time, link hover |
| Loading attribute | Images, iframes | <img loading="lazy"> |
Bundle Optimization
| Technique | Purpose | Tool |
|---|---|---|
| Tree shaking | Remove unused exports | Webpack, Rollup |
| Minification | Reduce code size | Terser, UglifyJS |
| Compression | Reduce transfer size | Gzip, Brotli |
| Scope hoisting | Flatten module scope | Webpack ModuleConcatenationPlugin |
| Bundle analysis | Visualize bundle size | webpack-bundle-analyzer |
Example: Dynamic imports
// Basic dynamic import
button.addEventListener('click', async () => {
const module = await import('./heavyModule.js');
module.doSomething();
});
// Import specific exports
const {default: Component} = await import('./Component.js');
const {helper1, helper2} = await import('./helpers.js');
// Conditional import
async function loadAnalytics() {
if (user.hasConsent) {
const {init} = await import('./analytics.js');
init();
}
}
// Route-based code splitting
const routes = {
'/home': () => import('./pages/Home.js'),
'/about': () => import('./pages/About.js'),
'/contact': () => import('./pages/Contact.js')
};
async function navigate(path) {
const loader = routes[path];
if (loader) {
const module = await loader();
const Page = module.default;
renderPage(Page);
}
}
// Component lazy loading with React
import React, {lazy, Suspense} from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
// Lazy load on interaction
class LazyButton {
constructor(element) {
this.element = element;
this.loaded = false;
// Load on first click
element.addEventListener('click', this.onClick.bind(this), {once: true});
}
async onClick(event) {
if (!this.loaded) {
const {handler} = await import('./buttonHandler.js');
this.loaded = true;
// Call the handler
handler(event);
// Future clicks go directly to handler
this.element.addEventListener('click', handler);
}
}
}
// Prefetch on hover
link.addEventListener('mouseenter', async () => {
// Prefetch when user hovers (likely to click)
await import('./nextPage.js');
}, {once: true});
// Webpack magic comments for chunking
const LazyComponent = lazy(() =>
import(
/* webpackChunkName: "heavy-component" */
/* webpackPrefetch: true */
'./HeavyComponent'
)
);
Example: Intersection Observer for lazy loading
// Lazy load images
class LazyImageLoader {
constructor(options = {}) {
this.rootMargin = options.rootMargin || '50px';
this.threshold = options.threshold || 0.01;
this.observer = new IntersectionObserver(
this.onIntersection.bind(this),
{
rootMargin: this.rootMargin,
threshold: this.threshold
}
);
}
observe(element) {
this.observer.observe(element);
}
onIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
this.observer.unobserve(entry.target);
}
});
}
loadImage(img) {
const src = img.dataset.src;
const srcset = img.dataset.srcset;
if (src) {
img.src = src;
}
if (srcset) {
img.srcset = srcset;
}
img.classList.add('loaded');
}
}
// Usage
const loader = new LazyImageLoader();
const images = document.querySelectorAll('img[data-src]');
images.forEach(img => loader.observe(img));
// HTML:
// <img data-src="image.jpg" alt="Lazy loaded image">
// Lazy load components
class LazyComponentLoader {
constructor() {
this.observer = new IntersectionObserver(
this.onIntersection.bind(this),
{rootMargin: '100px'}
);
this.components = new Map();
}
register(element, loader) {
this.components.set(element, loader);
this.observer.observe(element);
}
async onIntersection(entries) {
for (const entry of entries) {
if (entry.isIntersecting) {
const element = entry.target;
const loader = this.components.get(element);
if (loader) {
await this.loadComponent(element, loader);
this.observer.unobserve(element);
this.components.delete(element);
}
}
}
}
async loadComponent(element, loader) {
try {
element.classList.add('loading');
const module = await loader();
const Component = module.default;
// Render component
const instance = new Component(element);
instance.render();
element.classList.remove('loading');
element.classList.add('loaded');
} catch (error) {
console.error('Failed to load component:', error);
element.classList.add('error');
}
}
}
// Usage
const componentLoader = new LazyComponentLoader();
const heavySection = document.getElementById('heavy-section');
componentLoader.register(
heavySection,
() => import('./HeavySection.js')
);
// Native lazy loading for images
// <img src="image.jpg" loading="lazy" alt="...">
// Polyfill for browsers without native support
if ('loading' in HTMLImageElement.prototype) {
// Native lazy loading supported
const images = document.querySelectorAll('img[loading="lazy"]');
// Nothing to do
} else {
// Fallback to Intersection Observer
const images = document.querySelectorAll('img[loading="lazy"]');
const loader = new LazyImageLoader();
images.forEach(img => {
img.dataset.src = img.src;
img.removeAttribute('src');
loader.observe(img);
});
}
Example: Module loading strategies
// Module loader with caching
class ModuleLoader {
constructor() {
this.cache = new Map();
this.loading = new Map();
}
async load(path) {
// Return cached module
if (this.cache.has(path)) {
return this.cache.get(path);
}
// Return existing promise if already loading
if (this.loading.has(path)) {
return this.loading.get(path);
}
// Start loading
const promise = import(path)
.then(module => {
this.cache.set(path, module);
this.loading.delete(path);
return module;
})
.catch(error => {
this.loading.delete(path);
throw error;
});
this.loading.set(path, promise);
return promise;
}
preload(paths) {
paths.forEach(path => {
if (!this.cache.has(path) && !this.loading.has(path)) {
this.load(path).catch(() => {
// Ignore preload errors
});
}
});
}
clear(path) {
this.cache.delete(path);
this.loading.delete(path);
}
}
const moduleLoader = new ModuleLoader();
// Load module
const module = await moduleLoader.load('./module.js');
// Preload modules
moduleLoader.preload([
'./page1.js',
'./page2.js',
'./page3.js'
]);
// Priority-based loading
class PriorityLoader {
constructor() {
this.queues = {
high: [],
medium: [],
low: []
};
this.processing = false;
}
load(path, priority = 'medium') {
return new Promise((resolve, reject) => {
this.queues[priority].push({
path,
resolve,
reject
});
this.process();
});
}
async process() {
if (this.processing) return;
this.processing = true;
while (this.hasWork()) {
const item = this.getNext();
if (item) {
try {
const module = await import(item.path);
item.resolve(module);
} catch (error) {
item.reject(error);
}
}
// Yield to browser
await new Promise(resolve => setTimeout(resolve, 0));
}
this.processing = false;
}
hasWork() {
return this.queues.high.length > 0 ||
this.queues.medium.length > 0 ||
this.queues.low.length > 0;
}
getNext() {
if (this.queues.high.length > 0) {
return this.queues.high.shift();
}
if (this.queues.medium.length > 0) {
return this.queues.medium.shift();
}
if (this.queues.low.length > 0) {
return this.queues.low.shift();
}
return null;
}
}
const priorityLoader = new PriorityLoader();
// Load critical module with high priority
await priorityLoader.load('./critical.js', 'high');
// Load nice-to-have module with low priority
priorityLoader.load('./analytics.js', 'low');
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. V8 Engine Optimization Techniques
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 structure
class Point {
constructor(x, y) {
this.x = x; // Always same order
this.y = y; // Always same properties
}
}
// BAD: Inconsistent object structure
function 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 structure
function 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 undefined
const 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;
}
Example: Array optimization
// BAD: Mixed types (generic array storage)
const mixed = [1, 'two', {three: 3}, null];
// V8 can't optimize - uses generic storage
// GOOD: Homogeneous types (optimized storage)
const numbers = [1, 2, 3, 4, 5];
const strings = ['a', 'b', 'c'];
const objects = [{id: 1}, {id: 2}, {id: 3}];
// BAD: Sparse array (dictionary mode)
const sparse = [];
sparse[0] = 'a';
sparse[1000] = 'b'; // Creates sparse array
// V8 switches to hash table storage
// GOOD: Dense array
const dense = new Array(1001).fill(null);
dense[0] = 'a';
dense[1000] = 'b';
// BAD: Holey array (slower access)
const holey = [1, 2, 3];
holey[10] = 11; // Creates holes at 3-9
// GOOD: Pre-size and fill
const filled = Array(11).fill(0);
filled[0] = 1;
filled[1] = 2;
filled[2] = 3;
filled[10] = 11;
// Array access patterns
// GOOD: Sequential access
function sumSequential(arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// GOOD: forEach (also optimized)
function sumForEach(arr) {
let sum = 0;
arr.forEach(x => {
sum += x;
});
return sum;
}
// Array element kinds (fastest to slowest)
// PACKED_SMI_ELEMENTS (small integers)
const smi = [1, 2, 3, 4, 5];
// PACKED_DOUBLE_ELEMENTS (doubles)
const doubles = [1.1, 2.2, 3.3, 4.4, 5.5];
// PACKED_ELEMENTS (objects)
const objects2 = [{}, {}, {}];
// HOLEY_SMI_ELEMENTS (holes + small integers)
const holeSmi = [1, 2, , 4, 5];
// DICTIONARY_ELEMENTS (slowest)
const dict = [];
dict[100000] = 1;
Example: Function optimization
// BAD: Try-catch in hot path
function hotFunction(x) {
try {
return x * 2;
} catch (e) {
return 0;
}
}
// GOOD: Isolate try-catch
function hotFunction2(x) {
return x * 2; // Can be optimized
}
function safeHotFunction(x) {
try {
return hotFunction2(x);
} catch (e) {
return 0;
}
}
// BAD: Large function (won't inline)
function largeFunction(a, b, c, d, e) {
// 100+ lines of code
// ...many operations
return result;
}
// GOOD: Small functions (can inline)
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
function compute(a, b) {
return multiply(add(a, b), 2); // Inlined
}
// BAD: Type-changing return
function inconsistentReturn(flag) {
if (flag) {
return 42; // Number
}
return 'error'; // String - causes deopt
}
// GOOD: Consistent return type
function consistentReturn(flag) {
if (flag) {
return {success: true, value: 42};
}
return {success: false, error: 'Failed'};
}
// Function inlining budget
// Small functions (< 600 bytes bytecode) can be inlined
// BAD: Megamorphic call site (4+ different types)
function process(obj) {
return obj.getValue(); // If called with 4+ different shapes
}
// Call with many different object shapes
process(new ClassA());
process(new ClassB());
process(new ClassC());
process(new ClassD());
process(new ClassE()); // Goes megamorphic!
// GOOD: Monomorphic (1 type) or Polymorphic (2-3 types)
function processA(obj) {
return obj.getValue(); // Only ClassA
}
function processB(obj) {
return obj.getValue(); // Only ClassB
}
// Object allocation optimization
// GOOD: Allocate in function (can escape analysis)
function createLocal() {
const obj = {x: 1, y: 2};
return obj.x + obj.y; // obj doesn't escape
}
// Escape analysis can eliminate allocation
// GOOD: Predictable control flow
function predictable(x) {
if (x > 0) {
return x * 2;
}
return 0;
}
// BAD: Complex control flow
function unpredictable(x) {
switch (x % 7) {
case 0: return x * 1;
case 1: return x * 2;
case 2: return x * 3;
// ... many cases
}
}
Key Points: V8 uses hidden classes for object shape. Initialize
all properties at once. Avoid
delete, use = undefined. Keep arrays homogeneous (same
type).
Avoid sparse/holey arrays. Monomorphic functions (same types) optimize best. Small functions can be inlined.
Isolate try-catch from hot paths. Consistent return types prevent deoptimization.
Section 20 Summary: Memory Management and Performance
- Garbage Collection: Automatic mark-and-sweep, generational (young/old), V8 Orinoco
- Reachability: Objects GC'd when unreachable from roots (global, stack, closures)
- Memory Leaks: Globals, timers, listeners, closures, detached DOM, unbounded caches
- Prevention: Cleanup lifecycle, WeakMap/WeakSet, remove listeners, clear timers
- Detection: Chrome DevTools Memory, heap snapshots, performance.memory monitoring
- Performance: Minimize DOM access, batch reads/writes, debounce/throttle
- Profiling: performance.mark/measure, PerformanceObserver, Core Web Vitals (LCP, FID, CLS)
- Code Splitting: Dynamic import(), route/component-based, lazy loading with IntersectionObserver
- V8 Optimization: Hidden classes, monomorphic functions, homogeneous arrays, avoid delete
- Best Practices: Profile first, optimize bottlenecks, memoization, object pools, tree shaking