Kazi Rahamatullah
Kazi Rahamatullah
AboutProjectsBlogContact
UsesBooks
ResumeView CV
Kazi Rahamatullah

© Copyright 2026 Kazi Rahamatullah

AboutProjectsBlogBooksUses
Twitter/XGitHubProduct HuntCodeSandbox
Back to Blog
JavaScriptMemory ManagementGarbage CollectionPerformanceInterview Preparation

JavaScript Memory Management

How JavaScript allocates, uses, and releases memory — stack vs heap, garbage collection algorithms, memory leaks, prevention tips, and interactive visualizations.

Jul 3, 20268 min read
JavaScript Memory Management

Introduction

JavaScript manages memory automatically — you never call free() like in C. But automatic does not mean invisible. Understanding allocation, usage, and release helps you write faster apps, pass interviews, and avoid costly memory leaks in long-running SPAs.

This guide covers:

  1. The memory life cycle — allocate, use, release
  2. Stack vs heap — where values live
  3. Garbage collection — reference counting vs mark-and-sweep
  4. Memory leaks — common causes and fixes
  5. Best practices — strict mode, cleanup, profiling

Related reading: JavaScript Execution Context for stack frames and the call stack.

Course Reference

Quick index

#Section
1Memory life cycle
↳ Life cycle demo
2Stack vs heap
↳ Stack vs heap visualizer
3Garbage collection
↳ GC algorithms demo
4Memory leaks
↳ Memory leak lab
5Best practices

1. Memory life cycle

Every value in JavaScript goes through three stages.

StageWhat happens
AllocationEngine reserves memory when you create a value
UsageYour code reads, writes, or passes references to the value
ReleaseGC reclaims memory when no references remain
javascript
// 1. Allocation
const user = { id: 'u-1', name: 'Kazi' }
 
// 2. Usage
console.log(user.name)
user.role = 'admin'
sendToApi(user)
 
// 3. Release — when no references remain, GC can free the object
let cache = { data: [1, 2, 3] }
cache = null // removes reference; object becomes eligible for GC

Note

Setting a variable to null does not force immediate collection — it removes one reference so the object may become eligible for GC on the next cycle.

Interactive: memory life cycle

Memory Life Cycle

Step through allocation, usage, and release — the three stages every value goes through.

1const user = {
2 id: 'u-1',
3 name: 'Kazi',
4}

The engine reserves memory when you create values — primitives on the stack, objects on the heap.

Heap usage1 object(s)
user (stack ref)— allocated
{ id, name } (heap)— allocated
Allocate→Use→Release (GC)

Back to index


2. Stack vs heap

JavaScript uses two memory regions.

RegionStoresAccessLifetime
StackPrimitives, execution context framesFast, LIFOFreed when function returns
HeapObjects, arrays, functionsBy referenceFreed by GC when unreachable

Stack — primitives and frames

javascript
let count = 42 // number on stack
let active = true // boolean on stack
let label = 'memory' // string value (engine-dependent; often interned or heap-backed)

Each function call pushes a stack frame. Deep recursion without a base case causes stack overflow:

javascript
function recurse() {
  recurse()
}
// recurse() // RangeError: Maximum call stack size exceeded

Heap — objects and references

javascript
const config = { theme: 'dark', locale: 'en' }
const copy = config // copies reference, not the object
 
copy.theme = 'light'
console.log(config.theme) // 'light' — same heap object
javascript
function makeBuffer() {
  return new Uint8Array(1024 * 1024) // 1 MB on heap
}
 
let buffer = makeBuffer()
buffer = null // eligible for GC if nothing else references the array

Interactive: stack vs heap

Stack vs Heap Visualizer

Switch scenarios to see where primitives, references, and shared objects live in memory.

let count = 42

let active = true

let label = 'JS'

Stack

count42
activetrue
label"JS"

Fast, fixed-size frames. Freed when context pops.

Heap

No heap allocations — all values are primitives on the stack.

Objects and functions. Reclaimed by garbage collection when unreachable.

Primitive values are stored directly on the stack — copied by value.

Interview Answer

The stack holds primitive values and execution context frames — fixed size, automatically popped on return. The heap stores objects, arrays, and functions — dynamically allocated, accessed by reference, and reclaimed by garbage collection when unreachable from GC roots.

Back to index


3. Garbage collection

JavaScript engines run automatic garbage collection — you do not manually free heap memory. Modern engines (V8, SpiderMonkey, JavaScriptCore) use optimized generational mark-and-sweep variants.

GC roots

Collection starts from root objects the program can always reach:

  • Global object (window / global)
  • Variables on the call stack
  • Closures and active execution contexts

Anything not reachable from a root is considered garbage.

Reference counting (conceptual)

Each object tracks how many references point to it. When count hits zero, memory is freed.

Flaw: circular references never reach zero:

javascript
function createCycle() {
  const a = {}
  const b = {}
  a.ref = b
  b.ref = a
  return 'done'
}
createCycle()
// a and b reference each other but are unreachable from global — reference counting alone would leak

Mark and sweep (modern approach)

  1. Mark — traverse from roots, mark every reachable object
  2. Sweep — free everything unmarked

Circular groups with no path from roots are collected correctly.

javascript
let cache = { data: new Array(1e6) }
cache = null // object becomes unreachable → swept on next GC cycle

Interactive: garbage collection

Garbage Collection Algorithms

Compare reference counting vs mark-and-sweep — and why modern engines use reachability from roots.

window / global

refs → user, cache

GC root

user

refs → profile

profile

cache

orphan

unreachable

detached

refs → orphan

unreachable

Mark-and-sweep starts from roots (global, call stack, closures). Reachable nodes are marked; everything else is swept — including circular groups with no root path.

AlgorithmRuleWeakness
Reference countingFree when count = 0Circular references
Mark and sweepFree unreachable from rootsPause times (mitigated by generational GC)

Performance

GC pauses can affect frame rate in animation-heavy UIs. Reduce allocations in hot loops, reuse objects, and avoid creating large temporary arrays every frame. Use Chrome DevTools Memory panel to record heap snapshots.

Back to index


4. Memory leaks

A memory leak is heap memory that is no longer needed but stays reachable — GC cannot reclaim it. Over time, retained memory grows and performance degrades.

Accidental globals

javascript
function init() {
  config = { debug: true } // implicit global in non-strict sloppy mode
}

Fix: 'use strict', const/let, and module scope.

Uncleared timers

javascript
const timerId = setInterval(() => {
  const payload = buildHeavyPayload()
  sendAnalytics(payload)
}, 5000)
 
// Missing: clearInterval(timerId) on page leave

Fix: clearInterval / clearTimeout in React cleanup or route teardown.

Orphan event listeners

javascript
window.addEventListener('scroll', handleScroll)
 
// Component unmounts but listener remains — closure keeps component state alive

Fix:

javascript
useEffect(() => {
  window.addEventListener('scroll', handleScroll)
  return () => window.removeEventListener('scroll', handleScroll)
}, [])

Detached DOM references

javascript
const cache = []
function onRender(el) {
  cache.push(el) // holds DOM nodes after removal from document
}

Fix: WeakSet / WeakMap, or clear the cache when nodes unmount.

javascript
const cache = new WeakSet()
function onRender(el) {
  cache.add(el) // weak reference — does not prevent GC
}

Interactive: memory leak lab

Memory Leak Lab

Toggle between leaky and fixed code — watch how retained references keep heap usage high.

1const id = setInterval(() => {
2 const huge = new Array(1e6)
3 process(huge)
4}, 1000)
5// never clearInterval(id)

The timer callback keeps firing and retaining new arrays on the heap.

Simulated heap pressure (leak)

90% retained

Memory leak — objects stay reachable and GC cannot reclaim them.

Warning

Memory leaks are especially painful in single-page apps that stay open for hours. Always clean up subscriptions, timers, listeners, and WebSocket connections in teardown logic.

Back to index


5. Best practices

Use strict mode and block scope

javascript
'use strict'
 
function process(items) {
  for (const item of items) {
    const result = transform(item) // block-scoped — released each iteration
    save(result)
  }
}

Clean up side effects

javascript
function connect(url) {
  const ws = new WebSocket(url)
  const heartbeat = setInterval(() => ws.send('ping'), 30_000)
 
  return () => {
    clearInterval(heartbeat)
    ws.close()
  }
}

Avoid retaining large closures unnecessarily

javascript
// Leaky — inner handler closes over entire hugeData
function setup(hugeData) {
  button.addEventListener('click', () => {
    console.log(hugeData.length)
  })
}
 
// Better — extract only what you need
function setup(hugeData) {
  const len = hugeData.length
  button.addEventListener('click', () => console.log(len))
}

Profile before optimizing

  1. Open Chrome DevTools → Memory
  2. Take a heap snapshot before and after an action
  3. Compare retained size and detached DOM nodes
  4. Look for growing arrays, closures, and event listener counts

Tip

In React, pair useEffect with a cleanup return. For fetch requests, use AbortController so in-flight responses do not update unmounted components or hold references.

javascript
useEffect(() => {
  const controller = new AbortController()
 
  fetch('/api/data', { signal: controller.signal })
    .then((res) => res.json())
    .then(setData)
    .catch((err) => {
      if (err.name !== 'AbortError') throw err
    })
 
  return () => controller.abort()
}, [])

Know when WeakMap / WeakSet help

Use weak collections for caches keyed by DOM nodes or objects you do not own — they do not prevent those objects from being collected.

Interview Answer

JavaScript uses automatic GC, primarily mark-and-sweep from roots. Memory leaks happen when unreachable-from-user-code values stay reachable from GC roots via globals, timers, listeners, or accidental references. Prevent with strict mode, cleanup functions, weak collections for caches, and heap profiling.

Back to index


Conclusion

JavaScript memory management follows a clear cycle: allocate when you create values, use them in your program, and release when garbage collection finds them unreachable.

Key takeaways:

  • Stack = primitives + call frames; heap = objects accessed by reference
  • GC marks reachable objects from roots, then sweeps the rest
  • Reference counting fails on circular refs; mark-and-sweep handles them
  • Leaks = forgotten globals, timers, listeners, and DOM refs
  • Prevent with strict mode, cleanup, weak collections, and profiling

Understanding memory is how you keep SPAs fast and production dashboards stable under load.


Have questions about memory leaks or GC? Reach out via the contact page.

Share this article

XLinkedInFacebook
Kazi Rahamatullah

Written by

Kazi Rahamatullah

FullStack Developer

X / TwitterGitHubLinkedIn

Subscribe to my newsletter

Stay up to date and get notified when I share new contents.

No spam ever, unsubscribe anytime