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. 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 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