Memory Management & Prototype System
📖 Concept
Memory Management in JavaScript uses automatic garbage collection (GC). The engine (V8, Hermes, JSC) allocates memory when objects are created and frees it when they're no longer reachable. But "automatic" doesn't mean "worry-free" — memory leaks are one of the top production issues in React Native.
Memory lifecycle:
- Allocation — When you declare variables, create objects, or call functions
- Usage — Reading/writing to allocated memory
- Deallocation — GC removes unreachable objects (no references pointing to them)
GC algorithms used by JS engines:
- Mark-and-Sweep (V8/Hermes primary): Starts from roots (global, stack), marks all reachable objects, sweeps unmarked ones
- Generational GC (V8): Young generation (nursery) for short-lived objects, old generation for long-lived. Most objects die young — optimize for that
- Incremental/Concurrent GC: GC runs in small slices to avoid long pauses
React Native Memory Leak Patterns:
- Unmounted component listeners — Event listeners not cleaned up in useEffect return
- Closure references — Callbacks holding references to unmounted component state
- Global caches — Unbounded caches that grow without eviction
- Circular references — Objects referencing each other preventing collection
- Native module leaks — Native allocations not released when JS objects are GC'd
The Prototype System:
Every JavaScript object has an internal [[Prototype]] link forming a chain. Property lookups traverse this chain. Understanding prototypes is essential because:
- React Native components extend React.Component through the prototype chain
- Performance: prototype method lookup is O(chain length)
classsyntax is syntactic sugar over prototypes- Understanding
thisbinding requires understanding prototype dispatch
💻 Code Example
1// === MEMORY LEAK PATTERNS IN REACT NATIVE ===23// ❌ LEAK: Event listener not cleaned up4function ChatScreen() {5 const [messages, setMessages] = useState([]);67 useEffect(() => {8 const subscription = EventEmitter.addListener('newMessage', (msg) => {9 setMessages(prev => [...prev, msg]);10 });11 // Missing: return () => subscription.remove();12 // When this screen unmounts, the listener persists,13 // holding a reference to setMessages → component → entire subtree14 }, []);15}1617// ✅ FIX: Always return cleanup18useEffect(() => {19 const subscription = EventEmitter.addListener('newMessage', handler);20 return () => subscription.remove();21}, []);2223// ❌ LEAK: Unbounded cache24const imageCache = new Map(); // Grows forever!25function cacheImage(url, bitmap) {26 imageCache.set(url, bitmap); // 50MB+ bitmaps accumulate27}2829// ✅ FIX: LRU cache with max size30class LRUCache<K, V> {31 private cache = new Map<K, V>();32 constructor(private maxSize: number) {}3334 get(key: K): V | undefined {35 const value = this.cache.get(key);36 if (value !== undefined) {37 // Move to end (most recently used)38 this.cache.delete(key);39 this.cache.set(key, value);40 }41 return value;42 }4344 set(key: K, value: V): void {45 this.cache.delete(key); // Remove if exists (for reorder)46 this.cache.set(key, value);47 if (this.cache.size > this.maxSize) {48 // Delete oldest (first) entry49 const firstKey = this.cache.keys().next().value;50 this.cache.delete(firstKey);51 }52 }53}5455// ✅ WeakRef for optional caching (GC can collect if needed)56const weakCache = new Map<string, WeakRef<object>>();57function getCachedOrFetch(key: string): object | null {58 const ref = weakCache.get(key);59 const cached = ref?.deref();60 if (cached) return cached;61 // Fetch fresh data...62 return null;63}6465// === PROTOTYPE SYSTEM ===6667// How class inheritance actually works:68class Animal {69 constructor(name) { this.name = name; }70 speak() { return `${this.name} makes a sound`; }71}7273class Dog extends Animal {74 speak() { return `${this.name} barks`; }75}7677const dog = new Dog('Rex');78// Prototype chain: dog → Dog.prototype → Animal.prototype → Object.prototype → null7980// Under the hood (what the engine does):81// dog.__proto__ === Dog.prototype82// Dog.prototype.__proto__ === Animal.prototype83// dog.speak() → found on Dog.prototype (no traversal needed)84// dog.toString() → not on dog, not on Dog.prototype,85// found on Object.prototype (2 hops)8687// Performance implication: deep prototype chains = slower property lookups88// In React Native, component hierarchies create deep chains:89// MyComponent → React.Component → Object90// Each render cycle traverses this chain for method resolution
🏋️ Practice Exercise
Memory & Prototype Exercises:
- Use Chrome DevTools (or Hermes profiler) to take heap snapshots before and after navigating to a screen and back — identify retained objects
- Implement an LRU cache with O(1) get/set using Map
- Create a memory leak deliberately (timer + closure) and then fix it
- Explain the prototype chain for:
const arr = [1,2,3]; arr.map(...)— where doesmapcome from? - Implement
Object.createfrom scratch to demonstrate prototype linkage - Profile your React Native app's memory usage over 5 minutes of typical use — identify the growth rate
⚠️ Common Mistakes
Assuming garbage collection handles everything — closures, event listeners, and native references can prevent collection
Using global Maps/Sets as caches without eviction — they grow unbounded and crash low-memory devices
Not cleaning up useEffect subscriptions — the #1 memory leak cause in React Native apps
Storing large binary data (images, buffers) in JS heap — use native storage or streaming APIs instead
Confusing proto with prototype — proto is the internal link on instances, prototype is the object on constructors used to set proto
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Memory Management & Prototype System. Login to unlock this feature.