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.
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();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}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}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}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}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}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}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}Memory management in JavaScript requires:
Proper memory management is the key to performant, scalable JavaScript applications.
`;
export const exercise_10_3 = `
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.
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 tells TypeScript: "this element exists at runtime, but you don't need to compile it — just trust me." We use it to describe: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!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 property1declare 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);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 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"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" />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';.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.