Stack vs Heap Explained: Rust, Go, Python, and JavaScript Compared

Aug 13, 2025
KK
Shivam MathurSenior Developer

Notes on how stack and heap work across languages

For those curious about a detailed stack vs heap comparison in Rust, you can find a dedicated deep dive here.

TL;DR

Stack: Small, fast, per-thread memory for fixed-size, short-lived data. Ideal for locals and parameters. Limited size, overflow aborts or errors.

Heap: Large, flexible, process-wide memory for dynamic-size or long-lived data. Slower allocation, possible fragmentation, garbage-collected or manually managed.

Language differences:

  • Rust: Deterministic destruction (Drop), you choose stack vs heap.
  • Go: Escape analysis decides; garbage-collected.
  • Python: Almost everything on heap; refcount + GC.
  • JavaScript: Almost everything on heap; GC with generational optimizations.

Deep dive: Detailed comparison and insights

Memory management model

  • Rust: Manual allocation choice; deterministic cleanup with ownership model; allocator is pluggable; default is system allocator.
  • Go: Runtime decides stack vs heap via escape analysis; GC reclaims heap memory; programmer can influence via code structure.
  • Python: Objects always heap-allocated; CPython uses reference counting for prompt cleanup and a cyclic GC for cycles.
  • JavaScript: Almost all values on heap; modern engines use generational, incremental, and concurrent garbage collectors.

Stack characteristics

  • Rust: Fixed-size per thread, guard page prevents silent overflow; overflow aborts; drop timing follows scope.
  • Go: Goroutine stacks start small (~2KB) and grow; frames may be moved during growth; pointer escapes trigger heap allocation.
  • Python: Locals on C stack are just references; actual objects on heap; recursion depth limit enforced.
  • JavaScript: Call stack bounded by engine; deep recursion throws RangeError.

Heap allocation and decision factors

  • Rust: Explicit types like Box, Vec, String; large or dynamically sized values go here.
  • Go: Escape analysis automatically promotes values to heap if needed; no manual override.
  • Python: All objects use heap; pymalloc optimizes small object allocation via arenas.
  • JavaScript: GC heap split into young/old generations; object lifetime influences where it lives.

Out-of-memory handling

  • Rust: Default handle_alloc_error panics; recoverable allocation possible via try_reserve.
  • Go: Fatal runtime error; process terminates.
  • Python: Raises MemoryError; recovery possible but rare in practice.
  • JavaScript: Process exits or tab crashes; no standard recovery.

Performance optimization patterns

  • Rust: Pre-sizing containers, arena allocators, SmallVec to avoid heap; avoid trait-object boxing in hot paths.
  • Go: Use make with capacity, minimize pointer escapes, reuse buffers via sync.Pool.
  • Python: Reuse buffers (bytearray, memoryview), prefer vectorized libraries, avoid unnecessary temporaries.
  • JavaScript: Keep object shapes stable, prefer typed arrays for numeric data, reuse arrays and objects.

Common pitfalls and risks

  • Rust: Stack overflow from deep recursion or large arrays; accidental heap allocations via trait objects.
  • Go: Hidden heap escapes from closures or interface conversions.
  • Python: Memory overhead per object; reference cycles if not broken.
  • JavaScript: Shape changes hurting JIT optimizations; memory leaks from closures or global references.

Tooling to inspect and debug

  • Rust: perf, heaptrack, cargo flamegraph, valgrind.
  • Go: pprof profiles, escape analysis (-gcflags="-m").
  • Python: tracemalloc, objgraph, memory_profiler.
  • JavaScript: Chrome DevTools memory profiles, Node’s --inspect and heap snapshots.

article
 
golang
 
javascript
 
performance
 
python
 
rust
 
tutorials