Memory and Garbage Collector in TypeScript: What's Behind the Scenes?
\n\nImagine you are building a house. You buy bricks, cement, rebar — but if you don't clean up the construction debris, the site will soon turn into a dump. The same thing happens in your TypeScript code. You create objects, arrays, closures — and if you don't free up memory in time, the application starts to slow down, and then crashes with an Out of Memory error.
In this article, we will break down how memory management in TypeScript works and its main tool — the garbage collector. You will learn what mechanisms are behind memory deallocation, how to avoid leaks, and why your code can "eat" megabytes even after you stop using it.
\n\nLet's go!
\n\nHow Does TypeScript Manage Memory? The Basics
\n\nLet's start with an important point: TypeScript is not a separate language, but a superset of JavaScript. This means that memory and the garbage collector in TypeScript work exactly the same as in JavaScript. TypeScript does not add new memory management mechanisms, but it gives us tools (types, interfaces) to write safer code.
\n\nIn JavaScript, memory is divided into two main areas:
\n\n- \n
- Stack — stores primitives (numbers, strings, booleans) and references to objects. It is automatically freed when a function exits. \n
- Heap — stores objects, arrays, functions. This is where the garbage collector works. \n
When you write:
\n\nconst user = { name: 'Alice', age: 30 };\n\nThe variable user is stored on the stack and contains a reference to the object in the heap. As long as there is a reference, the object is alive. Once the reference disappears, the object becomes "garbage".
Garbage Collector in TypeScript: How Does It Work?
\n\nThe garbage collector (GC) is an automatic process that finds objects that are no longer referenced and frees the memory they occupy. Modern engines (V8 in Chrome, SpiderMonkey in Firefox) use complex algorithms, but the foundation is the Mark-and-Sweep algorithm.
\n\nHow it works:
\n\n- \n
- The GC starts from "roots" — global objects, local variables in executing functions. \n
- It traverses all references from the roots and marks every reachable object. \n
- All unmarked objects are considered garbage and are removed. \n
Sounds simple, but in practice there are nuances. For example, circular references:
\n\nfunction createCycle() {\n const obj1: any = {};\n const obj2: any = {};\n obj1.ref = obj2;\n obj2.ref = obj1;\n return 'done';\n}\n\nIn the past, such structures led to leaks, but modern garbage collectors handle cycles perfectly — the main thing is that both objects are unreachable from the roots.
\n\nTypical Memory Leaks in TypeScript Applications
\n\nEven with a smart garbage collector, you can "step on a rake". Here are the most common scenarios that lead to memory leaks in TypeScript:
\n\n1. Forgotten Timers and Event Handlers
\n\nOne of the most common causes. You subscribed to an event but forgot to unsubscribe:
\n\nclass TimerComponent {\n private intervalId: number;\n\n start() {\n this.intervalId = window.setInterval(() => {\n console.log('Tick');\n }, 1000);\n }\n\n // Forgot to call stop() — the timer will live forever!\n}\n\nSolution: always clear timers and unsubscribe from events in the dispose or ngOnDestroy method (for Angular).
2. Closures That Hold References
\n\nClosures are a powerful tool, but they can accidentally hold onto huge objects:
\n\nfunction createHeavyClosure() {\n const hugeData = new Array(1000