We use cookies to enhance your experience on the site
CodeWorlds

Memory Management - Garbage Collection, Memory Leaks

Memory management in JavaScript is one of the most important aspects of application performance. Although JavaScript has an automatic garbage collector, developers must understand how memory works to avoid memory leaks and optimize the performance of their applications.

How Memory Works in JavaScript

Memory Lifecycle

1// Memory lifecycle in JavaScript
2const memoryLifecycle = {
3  // 1. Memory allocation - automatic
4  allocation: () => {
5    const obj = { name: 'John', age: 30 }; // Allocation in heap
6    const arr = [1, 2, 3, 4, 5]; // Allocation in heap
7    const num = 42; // Allocation in stack (primitive)
8
9    return { obj, arr, num };
10  },
11
12  // 2. Memory usage - read/write
13  usage: (data) => {
14    data.obj.name = 'Jane'; // Using allocated memory
15    data.arr.push(6);
16
17    console.log(data.obj, data.arr);
18  },
19
20  // 3. Memory release - automatic by GC
21  release: () => {
22    // When variables go out of scope, they become candidates for GC
23    // GC will run automatically at the appropriate time
24  }
25};
26
27// Demonstration of the cycle
28function demonstrateMemoryLifecycle() {
29  const data = memoryLifecycle.allocation(); // Allocation
30  memoryLifecycle.usage(data); // Usage
31  // After the function ends, 'data' becomes a candidate for GC
32}
33
34demonstrateMemoryLifecycle();

Stack vs Heap

1// Stack - stores primitives and references
2function stackExample() {
3  const a = 5; // Stack
4  const b = 'hello'; // Stack (reference to string in heap)
5  const c = true; // Stack
6
7  console.log('Stack variables:', a, b, c);
8} // After the function ends, everything from stack is removed
9
10// Heap - stores objects and arrays
11function heapExample() {
12  const obj = { x: 1, y: 2 }; // Object in heap, reference in stack
13  const arr = [1, 2, 3]; // Array in heap, reference in stack
14
15  // Modifying object in heap
16  obj.z = 3;
17  arr.push(4);
18
19  return { obj, arr }; // Returning references
20} // References from stack are removed, but objects in heap remain
21
22// Memory allocation visualization
23class MemoryVisualizer {
24  constructor() {
25    this.allocations = [];
26    this.currentId = 0;
27  }
28
29  allocate(type, size, description) {
30    const allocation = {
31      id: this.currentId++,
32      type: type, // 'stack' | 'heap'
33      size: size,
34      description: description,
35      timestamp: Date.now(),
36      freed: false
37    };
38
39    this.allocations.push(allocation);
40    console.log(`Allocated ${type}: ${description} (${size} bytes)`);
41
42    return allocation.id;
43  }
44
45  free(id) {
46    const allocation = this.allocations.find(a => a.id === id);
47    if (allocation) {
48      allocation.freed = true;
49      allocation.freedAt = Date.now();
50      console.log(`Freed: ${allocation.description}`);
51    }
52  }
53
54  getMemoryStats() {
55    const active = this.allocations.filter(a => !a.freed);
56    const freed = this.allocations.filter(a => a.freed);
57
58    return {
59      totalAllocations: this.allocations.length,
60      activeAllocations: active.length,
61      freedAllocations: freed.length,
62      totalActiveSize: active.reduce((sum, a) => sum + a.size, 0),
63      memoryLeaks: active.filter(a => Date.now() - a.timestamp > 30000) // > 30s
64    };
65  }
66}
67
68const memoryViz = new MemoryVisualizer();
69
70// Usage example
71function demonstrateMemoryAllocation() {
72  // Stack allocations
73  const stackId1 = memoryViz.allocate('stack', 8, 'number variable');
74  const stackId2 = memoryViz.allocate('stack', 8, 'string reference');
75
76  // Heap allocations
77  const heapId1 = memoryViz.allocate('heap', 64, 'object {name, age, city}');
78  const heapId2 = memoryViz.allocate('heap', 32, 'array [1,2,3,4,5]');
79
80  // Simulating memory release
81  setTimeout(() => {
82    memoryViz.free(stackId1);
83    memoryViz.free(stackId2);
84    console.log('Memory stats:', memoryViz.getMemoryStats());
85  }, 1000);
86
87  setTimeout(() => {
88    memoryViz.free(heapId1);
89    // heapId2 is not freed - simulating a memory leak
90    console.log('Final memory stats:', memoryViz.getMemoryStats());
91  }, 2000);
92}

Garbage Collection in JavaScript

Types of GC Algorithms

1// 1. Reference Counting (old algorithm)
2class ReferenceCountingDemo {
3  constructor() {
4    this.objects = new Map();
5  }
6
7  createObject(id, data) {
8    const obj = {
9      id: id,
10      data: data,
11      refCount: 1,
12      references: []
13    };
14
15    this.objects.set(id, obj);
16    console.log(`Created object ${id} with ref count: 1`);
17    return obj;
18  }
19
20  addReference(fromId, toId) {
21    const fromObj = this.objects.get(fromId);
22    const toObj = this.objects.get(toId);
23
24    if (fromObj && toObj) {
25      fromObj.references.push(toId);
26      toObj.refCount++;
27      console.log(`Object ${toId} ref count increased to: ${toObj.refCount}`);
28    }
29  }
30
31  removeReference(fromId, toId) {
32    const fromObj = this.objects.get(fromId);
33    const toObj = this.objects.get(toId);
34
35    if (fromObj && toObj) {
36      const index = fromObj.references.indexOf(toId);
37      if (index > -1) {
38        fromObj.references.splice(index, 1);
39        toObj.refCount--;
40        console.log(`Object ${toId} ref count decreased to: ${toObj.refCount}`);
41
42        // Auto GC when ref count reaches 0
43        if (toObj.refCount === 0) {
44          this.collectObject(toId);
45        }
46      }
47    }
48  }
49
50  collectObject(id) {
51    const obj = this.objects.get(id);
52    if (obj) {
53      // Recursively decrease reference counts
54      obj.references.forEach(refId => {
55        this.removeReference(id, refId);
56      });
57
58      this.objects.delete(id);
59      console.log(`Collected object ${id}`);
60    }
61  }
62
63  // Problem: Circular references
64  createCircularReference() {
65    const obj1 = this.createObject('circular1', 'First object');
66    const obj2 = this.createObject('circular2', 'Second object');
67
68    // Mutual references - memory leak in reference counting!
69    this.addReference('circular1', 'circular2');
70    this.addReference('circular2', 'circular1');
71
72    console.log('Created circular reference - will never be collected!');
73
74    return { obj1, obj2 };
75  }
76}
77
78// 2. Mark and Sweep (modern algorithm)
79class MarkAndSweepDemo {
80  constructor() {
81    this.objects = new Map();
82    this.roots = new Set(); // Global variables, stack references
83  }
84
85  createObject(id, data) {
86    const obj = {
87      id: id,
88      data: data,
89      references: [],
90      marked: false
91    };
92
93    this.objects.set(id, obj);
94    return obj;
95  }
96
97  addToRoots(id) {
98    this.roots.add(id);
99    console.log(`Added ${id} to GC roots`);
100  }
101
102  removeFromRoots(id) {
103    this.roots.delete(id);
104    console.log(`Removed ${id} from GC roots`);
105  }
106
107  addReference(fromId, toId) {
108    const fromObj = this.objects.get(fromId);
109    if (fromObj && !fromObj.references.includes(toId)) {
110      fromObj.references.push(toId);
111    }
112  }
113
114  // Mark phase - marks all reachable objects
115  markPhase() {
116    console.log('Starting mark phase...');
117
118    // Clear previous marks
119    this.objects.forEach(obj => obj.marked = false);
120
121    // Mark all objects reachable from roots
122    const visited = new Set();
123
124    const markRecursive = (id) => {
125      if (visited.has(id)) return;
126      visited.add(id);
127
128      const obj = this.objects.get(id);
129      if (obj) {
130        obj.marked = true;
131        console.log(`  Marked object ${id}`);
132
133        // Recursively mark references
134        obj.references.forEach(refId => markRecursive(refId));
135      }
136    };
137
138    // Start from all roots
139    this.roots.forEach(rootId => markRecursive(rootId));
140  }
141
142  // Sweep phase - removes unmarked objects
143  sweepPhase() {
144    console.log('Starting sweep phase...');
145
146    const toDelete = [];
147
148    this.objects.forEach((obj, id) => {
149      if (!obj.marked) {
150        toDelete.push(id);
151      }
152    });
153
154    toDelete.forEach(id => {
155      console.log(`Collecting object ${id}`);
156      this.objects.delete(id);
157    });
158
159    console.log(`Collected ${toDelete.length} objects`);
160  }
161
162  // Full GC cycle
163  runGarbageCollection() {
164    console.log('Running garbage collection...');
165    this.markPhase();
166    this.sweepPhase();
167    console.log(`Active objects: ${this.objects.size}`);
168  }
169
170  // Demonstration of resolving circular references
171  demonstrateCircularReferences() {
172    // Creating objects with circular references
173    const obj1 = this.createObject('mark1', 'Object 1');
174    const obj2 = this.createObject('mark2', 'Object 2');
175    const obj3 = this.createObject('mark3', 'Object 3');
176
177    // Circular references
178    this.addReference('mark1', 'mark2');
179    this.addReference('mark2', 'mark3');
180    this.addReference('mark3', 'mark1'); // Circular!
181
182    // Only obj1 is in roots
183    this.addToRoots('mark1');
184
185    console.log('Before GC - objects with circular references');
186    this.runGarbageCollection(); // All will be marked as reachable
187
188    // Remove from roots
189    this.removeFromRoots('mark1');
190
191    console.log('After removing from roots');
192    this.runGarbageCollection(); // All will be collected despite circular references!
193  }
194}

Generational Garbage Collection

1// Modern JS engines use generational GC
2class GenerationalGCDemo {
3  constructor() {
4    // Different generations of objects
5    this.youngGeneration = new Map(); // New objects
6    this.oldGeneration = new Map();   // Old objects
7    this.permanentGeneration = new Map(); // Very old objects
8
9    this.gcCycles = 0;
10    this.promotionThreshold = 3; // How many GC cycles to survive to be promoted
11  }
12
13  allocateObject(id, data, size = 64) {
14    const obj = {
15      id: id,
16      data: data,
17      size: size,
18      generation: 'young',
19      gcSurvived: 0,
20      allocatedAt: Date.now(),
21      lastAccessed: Date.now()
22    };
23
24    this.youngGeneration.set(id, obj);
25    console.log(`Allocated ${id} in young generation`);
26    return obj;
27  }
28
29  accessObject(id) {
30    let obj = this.youngGeneration.get(id) ||
31              this.oldGeneration.get(id) ||
32              this.permanentGeneration.get(id);
33
34    if (obj) {
35      obj.lastAccessed = Date.now();
36      console.log(`Accessed object ${id}`);
37    }
38
39    return obj;
40  }
41
42  // Minor GC - young generation only
43  runMinorGC() {
44    console.log('Running Minor GC (young generation)...');
45    this.gcCycles++;
46
47    const survivors = [];
48    const toPromote = [];
49
50    this.youngGeneration.forEach((obj, id) => {
51      // Simulation: objects accessed in the last 5 seconds survive
52      const timeSinceLastAccess = Date.now() - obj.lastAccessed;
53
54      if (timeSinceLastAccess < 5000) {
55        obj.gcSurvived++;
56        survivors.push({ id, obj });
57
58        // Promotion to old generation
59        if (obj.gcSurvived >= this.promotionThreshold) {
60          toPromote.push({ id, obj });
61        }
62
63        console.log(`Object ${id} survived (count: ${obj.gcSurvived})`);
64      } else {
65        console.log(`Collected young object ${id}`);
66      }
67    });
68
69    // Clear young generation
70    this.youngGeneration.clear();
71
72    // Restore survivors
73    survivors.forEach(({ id, obj }) => {
74      if (!toPromote.find(p => p.id === id)) {
75        this.youngGeneration.set(id, obj);
76      }
77    });
78
79    // Promote to old generation
80    toPromote.forEach(({ id, obj }) => {
81      obj.generation = 'old';
82      this.oldGeneration.set(id, obj);
83      console.log(`Promoted ${id} to old generation`);
84    });
85
86    console.log(`Minor GC completed. Young: ${this.youngGeneration.size}, Old: ${this.oldGeneration.size}`);
87  }
88
89  // Major GC - all generations
90  runMajorGC() {
91    console.log('Running Major GC (all generations)...');
92
93    const collectFromGeneration = (generation, name) => {
94      const survivors = new Map();
95      let collected = 0;
96
97      generation.forEach((obj, id) => {
98        const timeSinceLastAccess = Date.now() - obj.lastAccessed;
99
100        // Old generation has a longer lifetime
101        const threshold = name === 'old' ? 10000 : 5000;
102
103        if (timeSinceLastAccess < threshold) {
104          survivors.set(id, obj);
105        } else {
106          collected++;
107          console.log(`Collected ${name} object ${id}`);
108        }
109      });
110
111      generation.clear();
112      survivors.forEach((obj, id) => generation.set(id, obj));
113
114      return collected;
115    };
116
117    const youngCollected = collectFromGeneration(this.youngGeneration, 'young');
118    const oldCollected = collectFromGeneration(this.oldGeneration, 'old');
119
120    console.log(`Major GC completed. Collected ${youngCollected + oldCollected} objects`);
121    console.log(`Remaining - Young: ${this.youngGeneration.size}, Old: ${this.oldGeneration.size}`);
122  }
123
124  // Automatic GC based on heuristics
125  autoGC() {
126    const youngSize = this.youngGeneration.size;
127    const oldSize = this.oldGeneration.size;
128
129    // GC triggers
130    if (youngSize > 100) { // Many young objects
131      this.runMinorGC();
132    }
133
134    if (oldSize > 50 || this.gcCycles % 10 === 0) { // Many old objects or every 10 cycles
135      this.runMajorGC();
136    }
137  }
138
139  getGenerationStats() {
140    return {
141      young: this.youngGeneration.size,
142      old: this.oldGeneration.size,
143      permanent: this.permanentGeneration.size,
144      totalGCCycles: this.gcCycles
145    };
146  }
147}
148
149// Generational GC demonstration
150const genGC = new GenerationalGCDemo();
151
152// Simulation of allocation patterns
153function simulateAllocationPattern() {
154  // Creating many short-lived objects
155  for (let i = 0; i < 50; i++) {
156    genGC.allocateObject(`temp_${i}`, `Temporary object ${i}`);
157  }
158
159  // Creating a few long-lived objects
160  for (let i = 0; i < 5; i++) {
161    const longLived = genGC.allocateObject(`long_${i}`, `Long-lived object ${i}`);
162
163    // Simulating regular access
164    setInterval(() => {
165      genGC.accessObject(`long_${i}`);
166    }, 1000);
167  }
168
169  // Automatic GC every second
170  setInterval(() => {
171    genGC.autoGC();
172    console.log('Generation stats:', genGC.getGenerationStats());
173  }, 2000);
174}

Types of Memory Leaks

1. Global Variables

1// Problem: Uncontrolled global variables
2class GlobalVariableLeaks {
3  static demonstrateLeaks() {
4    // BAD: Accidental global variables
5    function accidentalGlobal() {
6      // Missing 'var', 'let', 'const' - creates a global variable!
7      leakedVariable = 'This will leak!';
8
9      // Also global via 'this' in non-strict mode
10      this.anotherLeak = { data: new Array(1000).fill('leak') };
11    }
12
13    accidentalGlobal();
14
15    // These variables will remain in memory until the end of the application
16    console.log('Leaked variables:', window.leakedVariable, window.anotherLeak);
17  }
18
19  static demonstrateSolutions() {
20    // GOOD: Use strict mode
21    'use strict';
22
23    function properFunction() {
24      // Compilation error instead of a global variable
25      // notDeclared = 'This will throw error in strict mode';
26
27      let properVariable = 'This is properly scoped';
28      const anotherProper = { data: 'safe' };
29
30      return { properVariable, anotherProper };
31    }
32
33    // GOOD: Module pattern
34    const safeModule = (function() {
35      let privateData = [];
36
37      return {
38        addData: function(item) {
39          privateData.push(item);
40        },
41
42        getData: function() {
43          return privateData.slice(); // Return copy
44        },
45
46        clear: function() {
47          privateData = [];
48        }
49      };
50    })();
51
52    return { safeModule };
53  }
54}
55
56// 2. Event Listeners
57class EventListenerLeaks {
58  constructor() {
59    this.data = new Array(10000).fill('memory consuming data');
60    this.handlers = new Map();
61  }
62
63  // BAD: Event listeners without cleanup
64  addLeakyListeners() {
65    const button = document.getElementById('leaky-button');
66
67    // Problem: listener references an object that may hold a lot of data
68    const handler = () => {
69      console.log('Clicked!', this.data.length);
70    };
71
72    button?.addEventListener('click', handler);
73
74    // When the component is removed, the listener remains!
75    // The entire object cannot be collected by GC
76  }
77
78  // GOOD: Proper cleanup
79  addProperListeners() {
80    const button = document.getElementById('proper-button');
81
82    const handler = (event) => {
83      console.log('Proper click!', event.target.id);
84    };
85
86    this.handlers.set('button-click', handler);
87    button?.addEventListener('click', handler);
88  }
89
90  cleanup() {
91    // Always remove event listeners during cleanup
92    this.handlers.forEach((handler, key) => {
93      if (key === 'button-click') {
94        const button = document.getElementById('proper-button');
95        button?.removeEventListener('click', handler);
96      }
97    });
98
99    this.handlers.clear();
100    console.log('Event listeners cleaned up');
101  }
102
103  // Modern approach: AbortController
104  addModernListeners() {
105    const controller = new AbortController();
106    const signal = controller.signal;
107
108    document.addEventListener('click', (event) => {
109      console.log('Modern click!', event.target);
110    }, { signal });
111
112    // Cleanup all listeners at once
113    setTimeout(() => {
114      controller.abort();
115      console.log('All listeners aborted');
116    }, 10000);
117  }
118}
119
120// 3. Timers and Intervals
121class TimerLeaks {
122  constructor() {
123    this.data = new Array(100000).fill('timer data');
124    this.timers = new Set();
125  }
126
127  // BAD: Timers without cleanup
128  createLeakyTimers() {
129    // Problem: setInterval references the entire class
130    const intervalId = setInterval(() => {
131      console.log('Leaky timer tick', this.data.length);
132    }, 1000);
133
134    // Timer is never cleared!
135    // Object cannot be collected by GC
136
137    return intervalId;
138  }
139
140  // GOOD: Tracked timers
141  createTrackedTimers() {
142    const intervalId = setInterval(() => {
143      console.log('Tracked timer tick');
144    }, 1000);
145
146    this.timers.add(intervalId);
147
148    // Auto cleanup after time
149    const timeoutId = setTimeout(() => {
150      this.clearTimer(intervalId);
151    }, 10000);
152
153    this.timers.add(timeoutId);
154
155    return intervalId;
156  }
157
158  clearTimer(timerId) {
159    clearInterval(timerId);
160    clearTimeout(timerId);
161    this.timers.delete(timerId);
162    console.log(`Timer ${timerId} cleared`);
163  }
164
165  cleanup() {
166    this.timers.forEach(timerId => {
167      clearInterval(timerId);
168      clearTimeout(timerId);
169    });
170
171    this.timers.clear();
172    console.log('All timers cleaned up');
173  }
174
175  // Modern approach: AbortController for async operations
176  createAbortableTimer() {
177    const controller = new AbortController();
178
179    const timer = {
180      start: () => {
181        const interval = setInterval(() => {
182          if (controller.signal.aborted) {
183            clearInterval(interval);
184            return;
185          }
186
187          console.log('Abortable timer tick');
188        }, 1000);
189
190        return interval;
191      },
192
193      stop: () => {
194        controller.abort();
195      }
196    };
197
198    return timer;
199  }
200}
201
202// 4. Closures
203class ClosureLeaks {
204  static demonstrateLeaks() {
205    function createLeakyClosures() {
206      const largeData = new Array(1000000).fill('leak data');
207
208      // Problem: closure holds a reference to the entire scope
209      return {
210        // Even if we only use a small part of the data
211        getSmallPart: function() {
212          return largeData.slice(0, 10);
213        },
214
215        // This function also keeps the entire largeData in memory
216        getLength: function() {
217          return largeData.length;
218        }
219      };
220    }
221
222    const leaky = createLeakyClosures();
223    console.log('Small part:', leaky.getSmallPart());
224
225    // largeData remains in memory even though we only need the length!
226    return leaky;
227  }
228
229  static demonstrateSolutions() {
230    function createOptimizedClosures() {
231      const largeData = new Array(1000000).fill('optimized data');
232
233      // GOOD: Extract only the needed data
234      const smallPart = largeData.slice(0, 10);
235      const dataLength = largeData.length;
236
237      // largeData can be collected by GC
238
239      return {
240        getSmallPart: function() {
241          return smallPart;
242        },
243
244        getLength: function() {
245          return dataLength;
246        }
247      };
248    }
249
250    // GOOD: Null references when done
251    function createCleanClosures() {
252      let data = new Array(100000).fill('clean data');
253
254      const api = {
255        process: function() {
256          const result = data.map(item => item.toUpperCase());
257          data = null; // Explicit cleanup!
258          return result;
259        }
260      };
261
262      return api;
263    }
264
265    return { createOptimizedClosures, createCleanClosures };
266  }
267}
268
269// 5. DOM References
270class DOMReferenceLeaks {
271  constructor() {
272    this.domReferences = new Map();
273    this.observers = [];
274  }
275
276  // BAD: Holding DOM references
277  createDOMLeaks() {
278    // Problem: references to removed DOM elements
279    const elements = document.querySelectorAll('.dynamic-content');
280
281    elements.forEach((element, index) => {
282      this.domReferences.set(`element_${index}`, {
283        node: element, // Direct reference!
284        data: new Array(1000).fill(`data_${index}`)
285      });
286    });
287
288    // Even if elements are removed from the DOM,
289    // they will remain in memory due to these references!
290  }
291
292  // GOOD: WeakMap for DOM references
293  createSafeDOMReferences() {
294    const elementData = new WeakMap();
295
296    const elements = document.querySelectorAll('.safe-content');
297
298    elements.forEach((element, index) => {
299      // WeakMap allows GC of element when it is removed from DOM
300      elementData.set(element, {
301        id: index,
302        data: new Array(1000).fill(`safe_data_${index}`)
303      });
304    });
305
306    return elementData;
307  }
308
309  // Proper cleanup for DOM observers
310  setupDOMObserver() {
311    const observer = new MutationObserver((mutations) => {
312      mutations.forEach(mutation => {
313        console.log('DOM changed:', mutation.type);
314      });
315    });
316
317    observer.observe(document.body, {
318      childList: true,
319      subtree: true
320    });
321
322    this.observers.push(observer);
323
324    // Auto cleanup
325    setTimeout(() => {
326      this.cleanupObservers();
327    }, 30000);
328  }
329
330  cleanupObservers() {
331    this.observers.forEach(observer => {
332      observer.disconnect();
333    });
334
335    this.observers = [];
336    console.log('DOM observers cleaned up');
337  }
338
339  cleanup() {
340    this.domReferences.clear();
341    this.cleanupObservers();
342  }
343}

Memory Leak Detection and Monitoring

1. Performance API for Memory Monitoring

1// Memory monitoring utilities
2class MemoryMonitor {
3  constructor() {
4    this.measurements = [];
5    this.isMonitoring = false;
6    this.alertThreshold = 50 * 1024 * 1024; // 50MB
7  }
8
9  startMonitoring(interval = 5000) {
10    if (this.isMonitoring) return;
11
12    this.isMonitoring = true;
13    console.log('Started memory monitoring');
14
15    const monitor = () => {
16      if (!this.isMonitoring) return;
17
18      const memInfo = this.getMemoryInfo();
19      this.measurements.push({
20        ...memInfo,
21        timestamp: Date.now()
22      });
23
24      this.checkMemoryAlerts(memInfo);
25      this.trimMeasurements();
26
27      setTimeout(monitor, interval);
28    };
29
30    monitor();
31  }
32
33  stopMonitoring() {
34    this.isMonitoring = false;
35    console.log('Stopped memory monitoring');
36  }
37
38  getMemoryInfo() {
39    if (performance.memory) {
40      return {
41        usedJSHeapSize: performance.memory.usedJSHeapSize,
42        totalJSHeapSize: performance.memory.totalJSHeapSize,
43        jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
44        usedPercent: (performance.memory.usedJSHeapSize / performance.memory.jsHeapSizeLimit) * 100
45      };
46    }
47
48    return {
49      usedJSHeapSize: 0,
50      totalJSHeapSize: 0,
51      jsHeapSizeLimit: 0,
52      usedPercent: 0
53    };
54  }
55
56  checkMemoryAlerts(memInfo) {
57    if (memInfo.usedJSHeapSize > this.alertThreshold) {
58      console.warn('High memory usage detected:', {
59        used: this.formatBytes(memInfo.usedJSHeapSize),
60        percent: memInfo.usedPercent.toFixed(2) + '%'
61      });
62
63      this.triggerMemoryCleanup();
64    }
65
66    // Check for memory leaks (constant growth)
67    if (this.measurements.length > 10) {
68      const recent = this.measurements.slice(-10);
69      const growth = recent[recent.length - 1].usedJSHeapSize - recent[0].usedJSHeapSize;
70      const timeSpan = recent[recent.length - 1].timestamp - recent[0].timestamp;
71      const growthRate = growth / timeSpan; // bytes per ms
72
73      if (growthRate > 1000) { // > 1KB/s growth
74        console.warn('Potential memory leak detected:', {
75          growthRate: this.formatBytes(growthRate * 1000) + '/s',
76          totalGrowth: this.formatBytes(growth)
77        });
78      }
79    }
80  }
81
82  triggerMemoryCleanup() {
83    // Force garbage collection if available (dev tools)
84    if (window.gc) {
85      window.gc();
86      console.log('Forced garbage collection');
87    }
88
89    // Cleanup event
90    window.dispatchEvent(new CustomEvent('memoryCleanup', {
91      detail: { memoryUsage: this.getMemoryInfo() }
92    }));
93  }
94
95  generateMemoryReport() {
96    if (this.measurements.length === 0) {
97      return { error: 'No measurements available' };
98    }
99
100    const latest = this.measurements[this.measurements.length - 1];
101    const oldest = this.measurements[0];
102
103    return {
104      currentUsage: {
105        used: this.formatBytes(latest.usedJSHeapSize),
106        total: this.formatBytes(latest.totalJSHeapSize),
107        limit: this.formatBytes(latest.jsHeapSizeLimit),
108        percent: latest.usedPercent.toFixed(2) + '%'
109      },
110      trend: {
111        totalGrowth: this.formatBytes(latest.usedJSHeapSize - oldest.usedJSHeapSize),
112        timeSpan: this.formatTime(latest.timestamp - oldest.timestamp),
113        measurementCount: this.measurements.length
114      },
115      recommendations: this.generateRecommendations()
116    };
117  }
118
119  generateRecommendations() {
120    const recommendations = [];
121    const latest = this.measurements[this.measurements.length - 1];
122
123    if (latest.usedPercent > 80) {
124      recommendations.push('Memory usage is very high - consider implementing cleanup strategies');
125    }
126
127    if (this.measurements.length > 5) {
128      const recentGrowth = this.measurements.slice(-5);
129      const avgGrowth = recentGrowth.reduce((sum, m, i) =>
130        i === 0 ? 0 : sum + (m.usedJSHeapSize - recentGrowth[i-1].usedJSHeapSize), 0
131      ) / (recentGrowth.length - 1);
132
133      if (avgGrowth > 1024 * 1024) { // > 1MB average growth
134        recommendations.push('Consistent memory growth detected - check for memory leaks');
135      }
136    }
137
138    return recommendations;
139  }
140
141  formatBytes(bytes) {
142    const units = ['B', 'KB', 'MB', 'GB'];
143    let size = bytes;
144    let unitIndex = 0;
145
146    while (size >= 1024 && unitIndex < units.length - 1) {
147      size /= 1024;
148      unitIndex++;
149    }
150
151    return `${size.toFixed(2)} ${units[unitIndex]}`;
152  }
153
154  formatTime(ms) {
155    const seconds = Math.floor(ms / 1000);
156    const minutes = Math.floor(seconds / 60);
157    const hours = Math.floor(minutes / 60);
158
159    if (hours > 0) return `${hours}h ${minutes % 60}m`;
160    if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
161    return `${seconds}s`;
162  }
163
164  trimMeasurements() {
165    // Keep only last 100 measurements
166    if (this.measurements.length > 100) {
167      this.measurements = this.measurements.slice(-100);
168    }
169  }
170}
171
172// Memory leak detector
173class MemoryLeakDetector {
174  constructor() {
175    this.snapshots = [];
176    this.leakPatterns = new Map();
177  }
178
179  takeSnapshot(label = 'snapshot') {
180    const snapshot = {
181      label: label,
182      timestamp: Date.now(),
183      memory: performance.memory ? {
184        used: performance.memory.usedJSHeapSize,
185        total: performance.memory.totalJSHeapSize
186      } : null,
187      objectCounts: this.countObjects()
188    };
189
190    this.snapshots.push(snapshot);
191    console.log(`Memory snapshot taken: ${label}`);
192
193    return snapshot;
194  }
195
196  countObjects() {
197    // Approximate object counting (in reality use dev tools)
198    const counts = {
199      arrays: 0,
200      objects: 0,
201      functions: 0,
202      strings: 0
203    };
204
205    // Simulation - in real implementation use heap snapshot API
206    return counts;
207  }
208
209  compareSnapshots(snapshot1Label, snapshot2Label) {
210    const snap1 = this.snapshots.find(s => s.label === snapshot1Label);
211    const snap2 = this.snapshots.find(s => s.label === snapshot2Label);
212
213    if (!snap1 || !snap2) {
214      console.error('Snapshots not found');
215      return null;
216    }
217
218    const comparison = {
219      memoryGrowth: snap2.memory ? snap2.memory.used - snap1.memory.used : 0,
220      timeSpan: snap2.timestamp - snap1.timestamp,
221      objectGrowth: {},
222      suspiciousGrowth: []
223    };
224
225    // Object growth analysis
226    Object.keys(snap1.objectCounts).forEach(type => {
227      const growth = snap2.objectCounts[type] - snap1.objectCounts[type];
228      comparison.objectGrowth[type] = growth;
229
230      if (growth > 100) { // Suspicious growth threshold
231        comparison.suspiciousGrowth.push({
232          type: type,
233          growth: growth,
234          growthRate: growth / (comparison.timeSpan / 1000) // per second
235        });
236      }
237    });
238
239    return comparison;
240  }
241
242  detectLeakPatterns() {
243    if (this.snapshots.length < 3) return [];
244
245    const patterns = [];
246    const recent = this.snapshots.slice(-3);
247
248    // Check for consistent memory growth
249    const memoryGrowth = recent.map((snap, i) =>
250      i === 0 ? 0 : snap.memory.used - recent[i-1].memory.used
251    ).slice(1);
252
253    const consistentGrowth = memoryGrowth.every(growth => growth > 0);
254
255    if (consistentGrowth) {
256      const avgGrowth = memoryGrowth.reduce((a, b) => a + b) / memoryGrowth.length;
257
258      patterns.push({
259        type: 'consistent_memory_growth',
260        severity: avgGrowth > 1024 * 1024 ? 'high' : 'medium',
261        description: `Memory consistently growing by ${(avgGrowth / 1024 / 1024).toFixed(2)}MB per measurement`,
262        avgGrowth: avgGrowth
263      });
264    }
265
266    return patterns;
267  }
268}
269
270// Global memory monitor instance
271const memoryMonitor = new MemoryMonitor();
272const leakDetector = new MemoryLeakDetector();
273
274// Usage example
275function startMemoryAnalysis() {
276  memoryMonitor.startMonitoring(2000); // Every 2 seconds
277
278  // Take initial snapshot
279  leakDetector.takeSnapshot('initial');
280
281  // Simulate some operations and take snapshots
282  setTimeout(() => {
283    // Simulate memory allocation
284    const data = new Array(100000).fill('test data');
285    leakDetector.takeSnapshot('after_allocation');
286  }, 5000);
287
288  setTimeout(() => {
289    // Generate memory report
290    const report = memoryMonitor.generateMemoryReport();
291    console.log('Memory Report:', report);
292
293    const patterns = leakDetector.detectLeakPatterns();
294    console.log('Leak Patterns:', patterns);
295  }, 10000);
296}

Best Practices for Memory Management

1. Proactive Memory Management

1// Memory-aware component design
2class MemoryAwareComponent {
3  constructor(config) {
4    this.config = config;
5    this.resources = new Map();
6    this.cleanup = new Set();
7    this.memoryBudget = config.memoryBudget || 10 * 1024 * 1024; // 10MB default
8  }
9
10  // Resource tracking
11  allocateResource(key, factory, size = 0) {
12    if (this.getCurrentMemoryUsage() + size > this.memoryBudget) {
13      this.performCleanup();
14    }
15
16    const resource = factory();
17    this.resources.set(key, {
18      data: resource,
19      size: size,
20      allocatedAt: Date.now(),
21      lastAccessed: Date.now()
22    });
23
24    return resource;
25  }
26
27  getResource(key) {
28    const resource = this.resources.get(key);
29    if (resource) {
30      resource.lastAccessed = Date.now();
31      return resource.data;
32    }
33    return null;
34  }
35
36  getCurrentMemoryUsage() {
37    return Array.from(this.resources.values())
38      .reduce((total, resource) => total + resource.size, 0);
39  }
40
41  performCleanup() {
42    console.log('Performing memory cleanup...');
43
44    const now = Date.now();
45    const staleThreshold = 5 * 60 * 1000; // 5 minutes
46
47    // Remove stale resources
48    for (const [key, resource] of this.resources) {
49      if (now - resource.lastAccessed > staleThreshold) {
50        this.deallocateResource(key);
51      }
52    }
53
54    // Run custom cleanup functions
55    this.cleanup.forEach(cleanupFn => {
56      try {
57        cleanupFn();
58      } catch (error) {
59        console.error('Cleanup function failed:', error);
60      }
61    });
62  }
63
64  deallocateResource(key) {
65    const resource = this.resources.get(key);
66    if (resource) {
67      // Custom cleanup if resource has cleanup method
68      if (resource.data && typeof resource.data.cleanup === 'function') {
69        resource.data.cleanup();
70      }
71
72      this.resources.delete(key);
73      console.log(`Deallocated resource: ${key}`);
74    }
75  }
76
77  addCleanupFunction(fn) {
78    this.cleanup.add(fn);
79  }
80
81  removeCleanupFunction(fn) {
82    this.cleanup.delete(fn);
83  }
84
85  destroy() {
86    // Cleanup all resources
87    this.resources.forEach((_, key) => {
88      this.deallocateResource(key);
89    });
90
91    // Run all cleanup functions
92    this.cleanup.forEach(cleanupFn => {
93      try {
94        cleanupFn();
95      } catch (error) {
96        console.error('Cleanup function failed during destroy:', error);
97      }
98    });
99
100    this.cleanup.clear();
101    console.log('Component destroyed and cleaned up');
102  }
103}
104
105// Object pooling for frequently used objects
106class ObjectPool {
107  constructor(factory, resetFn, initialSize = 10) {
108    this.factory = factory;
109    this.resetFn = resetFn;
110    this.pool = [];
111    this.activeObjects = new Set();
112
113    // Pre-allocate objects
114    for (let i = 0; i < initialSize; i++) {
115      this.pool.push(this.factory());
116    }
117  }
118
119  acquire() {
120    let obj;
121
122    if (this.pool.length > 0) {
123      obj = this.pool.pop();
124    } else {
125      obj = this.factory();
126      console.log('Created new object (pool exhausted)');
127    }
128
129    this.activeObjects.add(obj);
130    return obj;
131  }
132
133  release(obj) {
134    if (this.activeObjects.has(obj)) {
135      this.activeObjects.delete(obj);
136
137      // Reset object to initial state
138      if (this.resetFn) {
139        this.resetFn(obj);
140      }
141
142      this.pool.push(obj);
143    }
144  }
145
146  getStats() {
147    return {
148      poolSize: this.pool.length,
149      activeObjects: this.activeObjects.size,
150      totalObjects: this.pool.length + this.activeObjects.size
151    };
152  }
153}
154
155// Object pooling usage example
156const vectorPool = new ObjectPool(
157  () => ({ x: 0, y: 0, z: 0 }), // factory
158  (vector) => { vector.x = 0; vector.y = 0; vector.z = 0; }, // reset
159  50 // initial size
160);
161
162function performVectorCalculations() {
163  const vectors = [];
164
165  // Acquire vectors from pool instead of creating new ones
166  for (let i = 0; i < 100; i++) {
167    const vector = vectorPool.acquire();
168    vector.x = Math.random();
169    vector.y = Math.random();
170    vector.z = Math.random();
171    vectors.push(vector);
172  }
173
174  // Do calculations...
175
176  // Release vectors back to pool
177  vectors.forEach(vector => vectorPool.release(vector));
178
179  console.log('Vector pool stats:', vectorPool.getStats());
180}
181
182// WeakRef for optional caching
183class WeakRefCache {
184  constructor() {
185    this.cache = new Map();
186    this.finalizationRegistry = new FinalizationRegistry((key) => {
187      console.log(`Object with key '${key}' was garbage collected`);
188      this.cache.delete(key);
189    });
190  }
191
192  set(key, value) {
193    const weakRef = new WeakRef(value);
194    this.cache.set(key, weakRef);
195    this.finalizationRegistry.register(value, key);
196  }
197
198  get(key) {
199    const weakRef = this.cache.get(key);
200    if (!weakRef) return null;
201
202    const value = weakRef.deref();
203    if (value === undefined) {
204      // Object was garbage collected
205      this.cache.delete(key);
206      return null;
207    }
208
209    return value;
210  }
211
212  has(key) {
213    return this.get(key) !== null;
214  }
215
216  delete(key) {
217    this.cache.delete(key);
218  }
219
220  size() {
221    // Clean up dead references
222    for (const [key, weakRef] of this.cache) {
223      if (weakRef.deref() === undefined) {
224        this.cache.delete(key);
225      }
226    }
227
228    return this.cache.size;
229  }
230}

2. Memory Profiling Tools

1// Development memory profiling utilities
2class MemoryProfiler {
3  constructor() {
4    this.profiles = new Map();
5    this.isProfilingEnabled = process.env.NODE_ENV === 'development';
6  }
7
8  startProfile(name) {
9    if (!this.isProfilingEnabled) return;
10
11    const profile = {
12      name: name,
13      startTime: performance.now(),
14      startMemory: performance.memory ? {
15        used: performance.memory.usedJSHeapSize,
16        total: performance.memory.totalJSHeapSize
17      } : null,
18      snapshots: []
19    };
20
21    this.profiles.set(name, profile);
22    console.log(`Started memory profile: ${name}`);
23  }
24
25  snapshot(profileName, label) {
26    if (!this.isProfilingEnabled) return;
27
28    const profile = this.profiles.get(profileName);
29    if (!profile) return;
30
31    const snapshot = {
32      label: label,
33      timestamp: performance.now(),
34      memory: performance.memory ? {
35        used: performance.memory.usedJSHeapSize,
36        total: performance.memory.totalJSHeapSize
37      } : null
38    };
39
40    profile.snapshots.push(snapshot);
41    console.log(`Memory snapshot: ${profileName}.${label}`);
42  }
43
44  endProfile(name) {
45    if (!this.isProfilingEnabled) return;
46
47    const profile = this.profiles.get(name);
48    if (!profile) return;
49
50    profile.endTime = performance.now();
51    profile.endMemory = performance.memory ? {
52      used: performance.memory.usedJSHeapSize,
53      total: performance.memory.totalJSHeapSize
54    } : null;
55
56    const report = this.generateProfileReport(profile);
57    console.log(`Memory profile completed: ${name}`, report);
58
59    return report;
60  }
61
62  generateProfileReport(profile) {
63    const duration = profile.endTime - profile.startTime;
64
65    let memoryGrowth = 0;
66    if (profile.startMemory && profile.endMemory) {
67      memoryGrowth = profile.endMemory.used - profile.startMemory.used;
68    }
69
70    const snapshots = profile.snapshots.map((snapshot, index) => {
71      const prevSnapshot = index > 0 ? profile.snapshots[index - 1] :
72        { memory: profile.startMemory, timestamp: profile.startTime };
73
74      return {
75        label: snapshot.label,
76        timeDelta: snapshot.timestamp - prevSnapshot.timestamp,
77        memoryDelta: snapshot.memory && prevSnapshot.memory ?
78          snapshot.memory.used - prevSnapshot.memory.used : 0
79      };
80    });
81
82    return {
83      duration: duration.toFixed(2) + 'ms',
84      memoryGrowth: memoryGrowth,
85      memoryGrowthFormatted: this.formatBytes(memoryGrowth),
86      snapshots: snapshots,
87      recommendations: this.generateProfileRecommendations(profile, memoryGrowth)
88    };
89  }
90
91  generateProfileRecommendations(profile, memoryGrowth) {
92    const recommendations = [];
93
94    if (memoryGrowth > 1024 * 1024) { // > 1MB growth
95      recommendations.push('Significant memory growth detected - review object allocations');
96    }
97
98    if (profile.snapshots.length > 0) {
99      const biggestGrowth = Math.max(...profile.snapshots.map((s, i) => {
100        const prev = i > 0 ? profile.snapshots[i-1] : { memory: profile.startMemory };
101        return s.memory && prev.memory ? s.memory.used - prev.memory.used : 0;
102      }));
103
104      if (biggestGrowth > 512 * 1024) { // > 512KB single growth
105        recommendations.push('Large single allocation detected - consider chunking or streaming');
106      }
107    }
108
109    return recommendations;
110  }
111
112  formatBytes(bytes) {
113    if (bytes === 0) return '0 B';
114    const k = 1024;
115    const sizes = ['B', 'KB', 'MB', 'GB'];
116    const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));
117    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
118  }
119}
120
121// Global profiler instance
122const memoryProfiler = new MemoryProfiler();
123
124// Decorator for automatic function profiling
125function profileMemory(name) {
126  return function(target, propertyKey, descriptor) {
127    const originalMethod = descriptor.value;
128
129    descriptor.value = function(...args) {
130      const profileName = name || `${target.constructor.name}.${propertyKey}`;
131
132      memoryProfiler.startProfile(profileName);
133
134      try {
135        const result = originalMethod.apply(this, args);
136
137        if (result instanceof Promise) {
138          return result.finally(() => {
139            memoryProfiler.endProfile(profileName);
140          });
141        } else {
142          memoryProfiler.endProfile(profileName);
143          return result;
144        }
145      } catch (error) {
146        memoryProfiler.endProfile(profileName);
147        throw error;
148      }
149    };
150
151    return descriptor;
152  };
153}
154
155// Usage example
156class DataProcessor {
157  @profileMemory('processLargeDataset')
158  processLargeDataset(data) {
159    memoryProfiler.snapshot('processLargeDataset', 'start');
160
161    // Process data in chunks
162    const chunks = this.chunkArray(data, 1000);
163    const results = [];
164
165    chunks.forEach((chunk, index) => {
166      memoryProfiler.snapshot('processLargeDataset', `chunk_${index}`);
167
168      const processed = chunk.map(item => this.transformItem(item));
169      results.push(...processed);
170    });
171
172    memoryProfiler.snapshot('processLargeDataset', 'end');
173    return results;
174  }
175
176  chunkArray(array, size) {
177    const chunks = [];
178    for (let i = 0; i < array.length; i += size) {
179      chunks.push(array.slice(i, i + size));
180    }
181    return chunks;
182  }
183
184  transformItem(item) {
185    return { ...item, processed: true, timestamp: Date.now() };
186  }
187}

Summary

Memory management in JavaScript requires:

  1. Understanding Garbage Collection - how it works and when it runs
  2. Awareness of Memory Leaks - main causes and ways to avoid them
  3. Proactive approach - monitoring and cleanup
  4. Proper patterns - object pooling, WeakMap, AbortController
  5. Diagnostic tools - profiling and monitoring

Key principles:

  • Always clean up after yourself (event listeners, timers, observers)
  • Use WeakMap/WeakSet for optional references
  • Monitor memory usage in production applications
  • Implement object pooling for frequently allocated objects
  • Avoid accidental global variables
  • Test your application for memory leaks

Proper memory management is the key to performant, scalable JavaScript applications.

`;

export const exercise_10_3 = `

Declaration Files (.d.ts) and DefinitelyTyped

Imagine that in Jurassic Park, ancient artifacts were discovered — mysterious tablets with descriptions of dinosaur species, written in an unknown language. Scientists had to create "translation dictionaries" to understand what those tablets described. In the TypeScript world, declaration files (.d.ts) serve the role of such dictionaries — they describe the shape of JavaScript code that itself has no type information.

What Are .d.ts Files?

Declaration files are files with the

.d.ts
extension that contain only type information — without implementation. They serve to describe the shape of JavaScript libraries, external modules, and global objects.

1// file: dinosaur-tracker.d.ts
2// Describing an external JavaScript library
3
4declare module 'dinosaur-tracker' {
5  export interface DinosaurPosition {
6    id: string;
7    species: string;
8    latitude: number;
9    longitude: number;
10    lastSeen: Date;
11  }
12
13  export function trackDinosaur(id: string): DinosaurPosition;
14  export function getAllPositions(): DinosaurPosition[];
15  export function setAlert(species: string, radius: number): void;
16}

Thanks to this file, TypeScript knows what functions and types the

dinosaur-tracker
module exports, even though the module itself is written in plain JavaScript.

The declare Keyword

The

declare
keyword tells TypeScript: "this element exists at runtime, but you don't need to compile it — just trust me." We use it to describe:

Global Variables

1// Global variable available in the browser
2declare const PARK_CONFIG: {
3  name: string;
4  maxCapacity: number;
5  securityLevel: 'low' | 'medium' | 'high' | 'critical';
6};
7
8// Now we can use it with full type safety
9console.log(PARK_CONFIG.name);
10console.log(PARK_CONFIG.securityLevel);
11// PARK_CONFIG.unknownField; // Compilation error!

Global Functions

1// Function defined in an external script
2declare function initializeFence(
3  zone: string,
4  voltage: number
5): { active: boolean; zone: string };
6
7declare function emergencyShutdown(): void;
8
9// Usage with type safety
10const fence = initializeFence('raptor-paddock', 10000);
11console.log(fence.active); // OK
12// console.log(fence.power); // Error! No such property

Classes and Namespaces

1declare class SecuritySystem {
2  constructor(zones: string[]);
3  arm(zone: string): void;
4  disarm(zone: string): void;
5  getStatus(): Record<string, boolean>;
6}
7
8declare namespace ParkAPI {
9  interface Visitor {
10    id: string;
11    name: string;
12    ticket: 'standard' | 'vip' | 'researcher';
13  }
14
15  function registerVisitor(name: string, ticket: Visitor['ticket']): Visitor;
16  function getVisitorCount(): number;
17}
18
19// Usage
20const system = new SecuritySystem(['zone-a', 'zone-b']);
21system.arm('zone-a');
22
23const visitor = ParkAPI.registerVisitor('Alan Grant', 'researcher');
24console.log(visitor.ticket);

Writing Your Own Declaration Files

When you use a JavaScript library that doesn't have types, you need to write your own

.d.ts
file:

1// file: types/legacy-dino-db.d.ts
2// Describing an old JS library for managing a dinosaur database
3
4declare module 'legacy-dino-db' {
5  export interface DinosaurRecord {
6    id: string;
7    species: string;
8    diet: 'herbivore' | 'carnivore' | 'omnivore';
9    weight: number;
10    height: number;
11    dangerLevel: 1 | 2 | 3 | 4 | 5;
12  }
13
14  export interface QueryOptions {
15    limit?: number;
16    offset?: number;
17    sortBy?: keyof DinosaurRecord;
18    order?: 'asc' | 'desc';
19  }
20
21  export class DinoDB {
22    constructor(connectionString: string);
23    connect(): Promise<void>;
24    disconnect(): Promise<void>;
25    findAll(options?: QueryOptions): Promise<DinosaurRecord[]>;
26    findById(id: string): Promise<DinosaurRecord | null>;
27    insert(record: Omit<DinosaurRecord, 'id'>): Promise<DinosaurRecord>;
28    update(id: string, data: Partial<DinosaurRecord>): Promise<DinosaurRecord>;
29    delete(id: string): Promise<boolean>;
30  }
31
32  // Default export
33  export default DinoDB;
34}

DefinitelyTyped and @types Packages

DefinitelyTyped is a huge repository on GitHub containing declaration files for thousands of JavaScript libraries. Instead of writing your own

.d.ts
files, you can install ready-made types:

1// Installing types for popular libraries:
2// npm install --save-dev @types/express
3// npm install --save-dev @types/lodash
4// npm install --save-dev @types/node
5
6// After installing @types/express you can write:
7import express, { Request, Response, NextFunction } from 'express';
8
9const app = express();
10
11// TypeScript knows the types Request, Response, NextFunction
12app.get('/dinosaurs/:id', (req: Request, res: Response) => {
13  const dinoId: string = req.params.id;
14  res.json({ id: dinoId, species: 'T-Rex' });
15});
16
17// Without @types/express - no type information!
18// req, res would be of type "any"

How Does TypeScript Find Declaration Files?

TypeScript searches for types in the following order:

1// 1. Types built into the package ("types" field in package.json)
2// Many modern libraries have built-in .d.ts
3// e.g. axios, zod, prisma
4
5// 2. @types packages from node_modules/@types/
6// Automatically recognized by TypeScript
7// e.g. @types/react, @types/node
8
9// 3. Custom .d.ts files in the project
10// Configuration in tsconfig.json:
11// {
12//   "compilerOptions": {
13//     "typeRoots": ["./node_modules/@types", "./types"],
14//     "types": ["node", "express"]
15//   },
16//   "include": ["src/**/*", "types/**/*"]
17// }
18
19// 4. Triple-slash directives (rarely used)
20/// <reference types="node" />
21/// <reference path="./custom-types.d.ts" />

Ambient Module Declarations

Sometimes you need to declare a module that is not an npm package — for example, a CSS file, an image, or a JSON file:

1// file: types/assets.d.ts
2
3// Importing CSS files
4declare module '*.css' {
5  const classes: Record<string, string>;
6  export default classes;
7}
8
9// Importing image files
10declare module '*.png' {
11  const src: string;
12  export default src;
13}
14
15declare module '*.svg' {
16  const content: string;
17  export default content;
18}
19
20// Importing JSON files
21declare module '*.json' {
22  const value: Record<string, unknown>;
23  export default value;
24}
25
26// Now you can import these files with type safety:
27// import styles from './styles.css';
28// import logo from './logo.png';
29// import dinoData from './dinosaurs.json';

Extending Existing Types (Declaration Merging in .d.ts)

.d.ts
files allow extending types from external libraries:

1// file: types/express-extension.d.ts
2// Extending Express types with additional fields
3
4import 'express';
5
6declare module 'express' {
7  interface Request {
8    userId?: string;
9    parkZone?: string;
10    securityClearance?: 'visitor' | 'staff' | 'admin';
11  }
12}
13
14// Now in the application code:
15// app.use((req, res, next) => {
16//   req.userId = 'USR-001';       // OK - TypeScript knows this field
17//   req.parkZone = 'zone-a';      // OK
18//   req.securityClearance = 'staff'; // OK
19//   next();
20// });

Declaration files (.d.ts) and the DefinitelyTyped ecosystem are the foundations of working with TypeScript in the real world. Thanks to them, you can use thousands of JavaScript libraries with full type safety — like scientists in Jurassic Park who, thanks to "translation dictionaries," could read even the oldest tablets with information about dinosaurs.

Go to CodeWorlds