# JavaScript Performance & Internals β€” Deep Dive

> V8 engine internals, JIT compilation, garbage collection, and performance optimization.

---

## 1. V8 Engine Architecture

V8 (Chrome/Node.js) is JavaScript's most influential engine. Here's how it works:

```
Source Code β†’ Parser β†’ AST β†’ Ignition (Interpreter) β†’ TurboFan (JIT Compiler) β†’ Optimized Machine Code
                                              ↕
                                      Feedback from Deoptimization
```

### Two Compilers, Two Speeds

| Stage | Compiler | Speed | Output |
|-------|----------|-------|--------|
| First | **Ignition** | Fast startup | Bytecode (interpreted) |
| Second | **TurboFan** | Optimized | Native machine code |

**Ignition** interprets bytecode immediately β€” fast startup.
**TurboFan** compiles "hot" functions to native code β€” maximum performance.

### How Functions Become Optimized

```
Function called once     β†’ Ignition interprets
Function called 10x     β†’ Still interpreted (collecting feedback)
Function called ~1000x  β†’ TurboFan kicks in (magical number)
Function deoptimized     β†’ Falls back to Ignition
```

### Deoptimization β€” When Optimizations Break

```javascript
function add(x, y) {
  return x + y;  // TurboFan: "x and y are always numbers β†’ use floating-point add"
}

add(1, 2);       // Optimized
add("1", "2");   // ❌ Deoptimized! TurboFan assumed numbers. Falls back to Ignition.
add(3, 4);       // Interpreted (may re-optimize if called enough)
```

**Key lesson:** Consistent data types = faster code.

---

## 2. Hidden Classes (Maps)

V8 uses hidden "maps" to optimize object property access.

### How V8 Optimizes Objects

```javascript
// Constructor pattern β€” ALL instances share the same map
function Point(x, y) {
  this.x = x;  // Properties added in same order
  this.y = y;
}

const a = new Point(1, 2);
const b = new Point(3, 4);
// βœ… a and b share the same hidden class β†’ O(1) property access

// Object literal with extra property β€” different map!
const c = { x: 5, y: 6, z: 7 };
// ❌ c has a different hidden class than a and b

// Adding properties later β€” triggers transition chain
const d = { x: 1 };
d.y = 2;  // Map transition: {x} β†’ {x,y}
d.z = 3;  // Map transition: {x,y} β†’ {x,y,z}
// Each transition creates a new hidden class
```

### Best Practices for V8 Optimization

```javascript
// βœ… Good β€” consistent shape
class User {
  constructor(name, email, age) {
    this.name = name;    // Always same order
    this.email = email;
    this.age = age;
  }
}

// ❌ Bad β€” inconsistent shape
class UserBad {
  constructor(name, email, age) {
    this.name = name;
    this.email = email;
    if (age) {           // Sometimes has 'age', sometimes doesn't
      this.age = age;
    }
  }
}

// βœ… Good β€” delete doesn't change shape (use null/undefined)
user.age = null;         // Keeps the same map

// ❌ Bad β€” delete creates new map
delete user.age;         // New transition!
```

---

## 3. Inline Caching

V8 caches property access patterns at each call site.

```javascript
const shapes = [
  { x: 1, y: 2, area() { return this.x * this.y; } },
  { x: 3, y: 4, area() { return this.x * this.y; } },
  { x: 5, y: 6, area() { return this.x * this.y; } },
];

// Monomorphic β€” all same shape β†’ fastest
shapes.forEach(s => console.log(s.area()));
// V8 caches: "shape.area β†’ function at address 0x..."

// Polymorphic β€” a few shapes β†’ still fast
const mixed = [...shapes, { x: 7, y: 8, area() { return this.x * this.y * 2; } }];

// Megamorphic β€” many shapes β†’ slow
const chaos = shapes.map((_, i) => ({ x: i, y: i, area() { return i * i; } }));
// Each has different map β†’ V8 gives up on caching
```

---

## 4. Garbage Collection

### V8's Two-Space GC

```
Young Generation (Nursery)     Old Generation
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  2-512MB  β”‚   ──survives──→ β”‚     Unbounded    β”‚
  β”‚  Scavenge  β”‚                  β”‚  Mark-Sweep-   β”‚
  β”‚  (fast)    β”‚                  β”‚  Compact       β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

### GC Generations

| Generation | Algorithm | Objects | Speed |
|-----------|-----------|---------|-------|
| **Young** | Scavenge (copying) | New objects, short-lived | Very fast (~ms) |
| **Old** | Mark-Sweep-Compact | Promoted objects, long-lived | Slower (~10s ms) |

### GC Generations β€” When Objects Promote

```javascript
// Object starts in Young Generation
const obj = { data: new Array(1000) };

// After 1-2 GC cycles, promoted to Old Generation
// If obj survives 2 scavenge cycles β†’ promoted

// Force old generation GC (Node.js)
// node --expose-gc index.js
// globalThis.gc();  // Full GC
```

### Memory Leak Patterns

```javascript
// ❌ Global variable leak
function leak() {
  leakedData = new Array(1000000);  // Implicit global!
}

// ❌ Forgotten timer
let timer = setInterval(() => {
  console.log('Still running...');
}, 1000);
// Never clearInterval β†’ keeps callback + closure alive

// ❌ Detached DOM elements
const element = document.createElement('div');
document.body.appendChild(element);
// ... later removed from DOM but referenced in variable
// element variable prevents GC

// ❌ Closures holding large data
function processData(largeArray) {
  const result = largeArray.reduce((sum, n) => sum + n, 0);
  
  return () => {
    console.log(result);  // βœ… Only captures result, not largeArray
    // If we referenced largeArray here, it would stay in memory
  };
}

// ❌ Event listeners on short-lived objects
element.addEventListener('click', handler);
// Element removed from DOM but listener still references it
```

### Detecting Memory Leaks

```javascript
// In DevTools Memory tab:
// 1. Take heap snapshot
// 2. Do action that might leak
// 3. Take another snapshot
// 4. Compare β€” look for growing object counts

// Programmatic check (Node.js)
import { performance } from 'perf_hooks';

function getMemoryUsage() {
  const used = process.memoryUsage();
  return {
    rss: Math.round(used.rss / 1024 / 1024),        // Resident set size (MB)
    heapTotal: Math.round(used.heapTotal / 1024 / 1024),
    heapUsed: Math.round(used.heapUsed / 1024 / 1024),
    external: Math.round(used.external / 1024 / 1024),
  };
}

console.log(getMemoryUsage());
// { rss: 50, heapTotal: 25, heapUsed: 18, external: 2 }
```

---

## 5. JavaScript Performance Anti-Patterns

### Synchronous Work on Main Thread

```javascript
// ❌ Blocks UI for 3+ seconds
function processLargeData(data) {
  const results = [];
  for (let i = 0; i < data.length; i++) {
    results.push(expensiveCalculation(data[i]));
  }
  return results;
}

// βœ… Split into chunks with microtask yield
async function processChunked(data, chunkSize = 100) {
  const results = [];
  for (let i = 0; i < data.length; i += chunkSize) {
    const chunk = data.slice(i, i + chunkSize);
    results.push(...chunk.map(expensiveCalculation));
    
    // Yield to browser β€” allows UI to update
    await new Promise(resolve => setTimeout(resolve, 0));
  }
  return results;
}

// βœ… Offload to Web Worker
const worker = new Worker('./processor.js');
worker.postMessage(largeData);
worker.onmessage = (e) => updateUI(e.data);
```

### String Concatenation in Loops

```javascript
// ❌ Creates N temporary strings
let result = '';
for (let i = 0; i < 10000; i++) {
  result += 'item' + i + ',';
}

// βœ… Array.join β€” single allocation
const parts = [];
for (let i = 0; i < 10000; i++) {
  parts.push('item', i);
}
const result = parts.join(',');

// βœ… StringBuilder pattern with array
const builder = [];
builder.push('<ul>');
items.forEach(item => builder.push(`<li>${item}</li>`));
builder.push('</ul>');
const html = builder.join('');
```

### DOM Manipulation

```javascript
// ❌ Forces layout recalculation on every iteration
items.forEach(item => {
  const div = document.createElement('div');
  div.textContent = item;
  container.appendChild(div);  // Triggers reflow each time
  console.log(div.offsetTop);  // Forces synchronous layout!
});

// βœ… DocumentFragment β€” single DOM insertion
const fragment = document.createDocumentFragment();
items.forEach(item => {
  const div = document.createElement('div');
  div.textContent = item;
  fragment.appendChild(div);
});
container.appendChild(fragment);  // Single reflow

// βœ… Even better: innerHTML
container.innerHTML = items.map(item => `<div>${item}</div>`).join('');

// βœ… requestAnimationFrame for visual updates
requestAnimationFrame(() => {
  container.innerHTML = generateHTML(items);
});
```

### Expensive DOM Queries in Loops

```javascript
// ❌ Queries DOM on every iteration
for (let i = 0; i < items.length; i++) {
  const list = document.getElementById('list');  // Query every time!
  const li = document.createElement('li');
  li.textContent = items[i];
  list.appendChild(li);
}

// βœ… Query once
const list = document.getElementById('list');
for (let i = 0; i < items.length; i++) {
  const li = document.createElement('li');
  li.textContent = items[i];
  list.appendChild(li);
}
```

---

## 6. Array Performance

### Method Comparison

```javascript
// For transforming data:
const arr = Array.from({ length: 1000000 }, (_, i) => i);

// βœ… .map() β€” optimized in V8
const doubled = arr.map(n => n * 2);

// ❌ .forEach() + push β€” slower
const doubled2 = [];
arr.forEach(n => doubled2.push(n * 2));

// βœ… For loop β€” fastest in V8
const doubled3 = new Array(arr.length);
for (let i = 0; i < arr.length; i++) {
  doubled3[i] = arr[i] * 2;
}

// .filter() β€” optimized
const evens = arr.filter(n => n % 2 === 0);

// .reduce() β€” slightly slower than for loop
const sum = arr.reduce((acc, n) => acc + n, 0);

// βœ… For loop sum β€” fastest
let sum2 = 0;
for (let i = 0; i < arr.length; i++) {
  sum2 += arr[i];
}
```

### Object vs Map Performance

```javascript
// Small number of lookups β†’ Object is fine
const dict = { a: 1, b: 2, c: 3 };
dict['a'];  // Fast

// Dynamic keys β†’ Map
const map = new Map();
map.set('key-with-hyphens', 1);
map.set(123, 2);  // Non-string keys work!
map.set(null, 3);  // Even null works!
map.has('key-with-hyphens');
map.get('key-with-hyphens');

// Large dataset with frequent lookups β†’ Map
// O(1) guaranteed vs Object's potential O(n)
```

### Set vs Array for Membership Testing

```javascript
const items = Array.from({ length: 100000 }, (_, i) => i);

// ❌ Array.includes β€” O(n)
items.includes(99999);  // Scans entire array

// βœ… Set.has β€” O(1)
const itemSet = new Set(items);
itemSet.has(99999);  // Instant

// Unique items
const unique = [...new Set([1, 2, 2, 3, 3, 3])];  // [1, 2, 3]
```

---

## 7. Debounce and Throttle

### Debounce β€” Wait for Idle

```javascript
// Run AFTER user stops doing the action for X ms
function debounce(fn, delay = 250) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Usage β€” search input
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
  fetchResults(e.target.value);  // Only fires 250ms after typing stops
}, 300));

// With leading edge
function debounceWithLeading(fn, delay = 250) {
  let timer;
  let canRun = true;
  return function(...args) {
    if (!canRun) return;
    fn.apply(this, args);
    canRun = false;
    clearTimeout(timer);
    timer = setTimeout(() => { canRun = true; }, delay);
  };
}
```

### Throttle β€” Limit Rate

```javascript
// Run AT MOST once every X ms
function throttle(fn, interval = 100) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// Usage β€” scroll handler
window.addEventListener('scroll', throttle(() => {
  console.log('Scroll position:', window.scrollY);
}, 100));

// Usage β€” resize handler
window.addEventListener('resize', throttle(() => {
  recalculateLayout();
}, 150));
```

### rAF Throttle β€” Sync with Display Refresh

```javascript
// Throttle to 60fps (or whatever the display refresh rate is)
function rAFThrottle(fn) {
  let ticking = false;
  return function(...args) {
    if (!ticking) {
      requestAnimationFrame(() => {
        fn.apply(this, args);
        ticking = false;
      });
      ticking = true;
    }
  };
}

window.addEventListener('scroll', rAFThrottle(() => {
  // Runs at most once per frame β€” perfectly synced with display
  updateScrollProgress();
}));
```

---

## 8. Lazy Loading Patterns

### Lazy Module Loading

```javascript
// Load module only when needed
let heavyModule;

async function doHeavyWork() {
  if (!heavyModule) {
    heavyModule = await import('./heavy-module.js');
  }
  return heavyModule.processData(data);
}

// Route-based code splitting (React example)
const Dashboard = lazy(() => import('./Dashboard.jsx'));
const Settings = lazy(() => import('./Settings.jsx'));
```

### Lazy Image Loading

```javascript
// Native lazy loading
<img src="photo.jpg" loading="lazy" alt="...">

// IntersectionObserver for custom behavior
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.add('loaded');
      observer.unobserve(img);
    }
  });
}, {
  rootMargin: '200px',  // Start loading 200px before visible
  threshold: 0.01,
});

document.querySelectorAll('[data-src]').forEach(img => observer.observe(img));
```

### Lazy Component Rendering

```javascript
// Render on first visibility
class LazyRender {
  constructor(container, renderFn) {
    this.container = container;
    this.renderFn = renderFn;
    this.rendered = false;
    this.setupObserver();
  }
  
  setupObserver() {
    this.observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting && !this.rendered) {
        this.rendered = true;
        this.renderFn(this.container);
        this.observer.disconnect();
      }
    }, { rootMargin: '500px' });
    
    this.observer.observe(this.container);
  }
}

// Usage
new LazyRender(
  document.getElementById('chart-container'),
  (container) => renderChart(container, data)
);
```

---

## 9. Memory Optimization

### Object Pooling

```javascript
// Reuse objects instead of creating/destroying
class ObjectPool {
  constructor(createFn, initialSize = 10) {
    this.createFn = createFn;
    this.pool = [];
    for (let i = 0; i < initialSize; i++) {
      this.pool.push(createFn());
    }
  }
  
  acquire() {
    return this.pool.pop() || this.createFn();
  }
  
  release(obj) {
    this.pool.push(obj);
  }
}

// Usage β€” particle system
const particlePool = new ObjectPool(
  () => ({ x: 0, y: 0, vx: 0, vy: 0, active: false }),
  100
);

function spawnParticle() {
  const p = particlePool.acquire();
  p.x = Math.random() * canvas.width;
  p.y = Math.random() * canvas.height;
  p.active = true;
  return p;
}

function cleanupParticle(p) {
  p.active = false;
  particlePool.release(p);  // Return to pool
}
```

### WeakMap for Caching

```javascript
// Cache that doesn't prevent garbage collection
const cache = new WeakMap();

function getMetadata(obj) {
  if (cache.has(obj)) return cache.get(obj);
  
  const metadata = expensiveAnalysis(obj);
  cache.set(obj, metadata);
  return metadata;
}

// When obj is no longer referenced elsewhere, cache entry is GC'd
```

### Transferable Objects

```javascript
// Transfer ArrayBuffer without copying
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
view.set([1, 2, 3, 4, 5, 6, 7, 8]);

// Transfer to worker (zero-copy)
worker.postMessage(buffer, [buffer]);
// buffer is now detached in main thread β€” cannot be read anymore

// Transfer between iframes
otherWindow.postMessage(buffer, [buffer]);

// PostMessage with transferable (ES2025)
// const newBuffer = buffer.transfer(16);  // Grow to 16 bytes
```

---

## 10. V8-Specific Optimizations

### Smi (Small Integer) Range

V8 stores small integers (31-bit) directly, not as objects.

```javascript
// βœ… Smi β€” stored directly, fastest
const a = 42;
const b = -1000000;

// ❌ Double β€” stored as heap object, slower
const c = 3.14;
const d = Number.MAX_SAFE_INTEGER + 1;

// ❌ BigInt β€” stored as heap object, slowest
const e = 42n;

// βœ… Keep loop counters as Smi
for (let i = 0; i < 1000000; i++) { /* i is a Smi */ }
```

### Inline Optimizations

```javascript
// βœ… Simple functions β€” likely inlined
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }

const result = add(multiply(3, 4), multiply(5, 6));
// V8 inlines all three β†’ single machine instruction

// ❌ Complex functions β€” unlikely to be inlined
function processData(a, b, c, d, e) {
  // ... 100 lines of code
  return result;
}
```

### Typed Arrays vs Regular Arrays

```javascript
// Regular array β€” each element is a JS object reference
const arr = [1, 2, 3, 4, 5];
// Memory: ~40 bytes per element + overhead

// Typed array β€” contiguous native memory
const typed = new Float64Array([1, 2, 3, 4, 5]);
// Memory: 8 bytes per element, no overhead

// Typed array is 5-10x faster for numerical computation
const N = 10000000;

// Regular array
const regular = new Array(N);
for (let i = 0; i < N; i++) regular[i] = i * 2;

// Typed array
const typed = new Float64Array(N);
for (let i = 0; i < N; i++) typed[i] = i * 2;
// Typed array: faster allocation, better cache locality
```

---

## 11. Bundle Size Optimization

### Tree Shaking

```javascript
// utils.js
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export function multiply(a, b) { return a * b; }
export function divide(a, b) { return a / b; }

// main.js β€” only imports add
import { add } from './utils.js';
// Bundler removes subtract, multiply, divide βœ…

// Side effects must be declared
// package.json
{ "sideEffects": false }  // Tells bundler all exports are pure
```

### Dynamic Import β€” Code Splitting

```javascript
// Split bundle into chunks
const routes = {
  dashboard: () => import('./pages/Dashboard.jsx'),
  settings: () => import('./pages/Settings.jsx'),
  analytics: () => import('./pages/Analytics.jsx'),
};

// Load chunk only when user navigates
const component = await routes[page]();
```

---

## Quick Reference: Performance Tips

| Issue | Solution | Impact |
|-------|----------|--------|
| Jank on scroll | `rAFThrottle` + `transform` | High |
| Memory leak | WeakMap, remove listeners | High |
| Slow list rendering | Virtual scroll / Lazy render | High |
| Main thread blocked | Web Workers, chunked processing | High |
| Slow lookups | Map/Set instead of Object/Array | Medium |
| GC pauses | Object pooling, avoid large objects | Medium |
| Slow computation | Typed Arrays, keep values as Smi | Medium |
| Large bundle | Tree shaking, dynamic imports | Medium |
| Layout thrashing | Batch DOM reads/writes | High |
| Slow image load | `loading="lazy"`, srcset, WebP | Medium |
| Slow font load | `font-display: swap`, preload | Medium |
| Slow startup | Code splitting, lazy modules | Medium |
| Deoptimization | Consistent object shapes | Medium |
| Memory pressure | Transferable objects | Medium |