Memory model: stack and heap
While a program runs, memory is split into two main regions.
Stack (call stack) — memory for function calls. Each call gets a stack frame: parameters, local variables, the return address. When the function ends, the frame is discarded immediately. The stack is fast (a pointer just moves back and forth) but limited in size (typically a few MB).
Heap — the large, free-form region where objects, arrays and collections live. Allocation is more expensive, and lifetime is not tied to a function returning — an object lives as long as something references it.
A simple rule: small fixed-size values on the stack, dynamically sized structures on the heap.
Value semantics vs reference semantics is the key to understanding data structures.
- Value: the variable holds the value itself. Assignment makes a copy. Examples:
int,double,boolean. - Reference: the variable holds the address of an object on the heap. Assignment copies the address — two variables look at the same object.
For example: write const a = { n: 1 } then const b = a — the address is copied, not the object. After b.n = 2, a.n is 2 as well, because a and b look at the same object on the heap.
This is why passing an object to a function lets that function mutate it: the pointer is copied, not the object. It is also the operating principle of linked lists, trees and graphs — nodes are wired together by references.
| Concept | JavaScript | Kotlin | Dart |
|---|---|---|---|
| Primitive values | number, string, boolean, null, undefined, symbol, bigint — value semantics | Int, Double, Boolean etc. — on the stack when the JVM can, boxed on the heap otherwise | Technically everything is an object, but int/double/bool/String are immutable and behave like values |
| Objects | Object, Array, Map, Set, functions — heap, reference semantics | All class instances live on the heap, reference semantics | All objects live on the heap, reference semantics |
| Null safety | None — `undefined`/`null` blow up at runtime | In the type system: `String` vs `String?`, `?.`, `?:`, checked by the compiler | Sound null safety: `String` vs `String?`, `!`, `??`, checked by the compiler |
| Shallow copy | `{...obj}`, `[...arr]`; `structuredClone()` for a deep copy | `copy()` on a `data class` (shallow!), `toList()` | `List.of(x)`, `Map.of(m)`, `{...map}` |
| Garbage collection | Generational, mark-and-sweep (the V8 engine) | The JVM's GC — G1, ZGC and others | Generational; each isolate has its own GC |
Garbage collection (GC) is the automatic reclaiming of heap objects nothing references any more. All three languages (JS, Kotlin/JVM, Dart) are garbage collected; you never call free().
Modern GCs work by reachability, not reference counting: the GC walks from roots (globals, live stack frames) and removes everything it cannot reach. That's why reference cycles (a.next = b; b.prev = a) are not a problem — if both are detached from the roots, both are collected.
Memory leaks happen in GC languages too: an object is no longer needed but something still references it. Typical causes: an ever-growing global cache, event listeners that are never removed, a timer or subscription still holding a closed object.
Interview tip. The most frequent question: "Why does `arr.push(4)` work on `const arr = [1,2,3]`?" A strong answer: const freezes the binding (which address the variable points at), not the object; arr = [] would throw, arr.push(4) won't. Explaining it with the "address on the stack, object on the heap" picture lands very well.
The second classic: "When do you get a stack overflow?" — on very deep or infinite recursion, because every call takes a frame and the stack is bounded (roughly 10,000 frames in JS). Add that some languages solve this with tail-call optimisation, but most JS runtimes don't implement it in practice — so deep recursion must be rewritten iteratively or with an explicit stack.
The most common mistake: mistaking a shallow copy for a deep one. {...obj} copies only the first level; nested objects are still shared.
📚 Sources and documentation
- Memory managementofficialdeveloper.mozilla.org