HomeVideos

99% of Devs Don't Get The Heap

Now Playing

99% of Devs Don't Get The Heap

Transcript

285 segments

0:00

99% of developers don't get heap memory.

0:03

Ask yourself this, is this on the heap

0:05

or the stack? At first glance, it might

0:07

look trivial, but if you guessed stack,

0:09

you're right. Well, mostly. The subtlety

0:12

is that returning a pointer to a local

0:14

variable is actually undefined behavior.

0:17

And if you tried storing data that

0:18

outlives the function, you'd need the

0:20

heap to avoid a dangling pointer. Did

0:22

you catch this critical bug? Let me know

0:24

in the comments below. Most developers

0:26

overlook the intricate behaviors that

0:28

make heap memory fascinating. Things

0:30

like fragmentation, coalescing free

0:32

blocks, cache locality effects, and

0:35

allocator strategies that can

0:36

dramatically change performance. You

0:38

might think that memory management is

0:39

just allocate and free. And you might

0:41

not care about these low-level details,

0:43

which are themselves very nuanced. But

0:45

if you don't know all the jargon that I

0:47

just said or just want a deep dive into

0:49

heap memory, this video is for you. So,

0:52

what is heap memory? Heap memory is a

0:54

region of a program's memory used for

0:57

dynamic allocation. Meaning data whose

1:00

size or lifetime cannot be determined at

1:02

compile time. When your program runs and

1:05

needs memory on demand for objects that

1:07

may outlive the current function or

1:09

collections whose size can grow, the

1:11

heap is where that memory comes from.

1:13

Unlike the stack which works in a very

1:15

structured lastin first outway, the heap

1:17

is a flexible but more expensive space

1:20

managed by an allocator or garbage

1:22

collector. So what is the real

1:24

distinction between the heap and the

1:26

stack? The stack is very tightly

1:28

managed. Each function call pushes a

1:31

frame with local variables and returns

1:33

pop them. It's extremely fast because

1:35

the compiler always knows exactly where

1:37

values are stored and how long they

1:40

live. The heap, by contrast, is managed

1:42

at runtime. Allocations require

1:45

bookkeeping, metadata, and sometimes

1:47

garbage collection, which makes them

1:49

much slower. The key difference is

1:50

lifetime. Stack variables disappear when

1:53

the function ends, but heap objects can

1:55

outlive the function that created them.

1:56

Here's an example in C. Is this created

1:59

on the stack or the heap? X= 10 is

2:02

created on the stack. Its lifetime ends

2:04

when the function returns. How about

2:06

this? Is this created on the stack or

2:08

the heap? You can see that there's a

2:10

call to maloc. So maloc is designed to

2:13

manage memory in the heap to provide

2:14

dynamic memory allocation. It allows

2:17

programs to request memory during

2:18

runtime for data whose size or lifetime

2:21

is not known at compile time. And this

2:24

memory on the heap persists until it is

2:26

explicitly deallocated using free which

2:28

gives the programmer control over an

2:30

object's lifetime. So what you have to

2:32

remember is the size of variables on the

2:34

stack must be fixed and known at compile

2:37

time with the exception of variable

2:39

length arrays in C99. The heap allows

2:41

for allocating data of arbitrary or

2:44

unknown size at runtime such as an array

2:46

whose size is determined by user input.

2:49

The stack has a relatively small fixed

2:51

size, typically a few megabytes.

2:54

Attempting to allocate large amounts of

2:55

data on the stack can quickly cause a

2:57

stack overflow error. The heap is a much

3:00

larger and more flexible memory region

3:02

that can grow as needed by requesting

3:04

more memory from the operating system.

3:06

But how does heap allocation actually

3:08

work? When your program asks for heap

3:10

memory, for example, with new maloc or

3:12

creating an object in Python or Go, the

3:14

runtime doesn't just grab random RAM. It

3:17

maintains structures like free lists,

3:19

size bins, or arenas that track which

3:22

blocks of memory are available. When you

3:24

allocate something, it finds a block

3:26

large enough, marks it as in use, and

3:28

returns a pointer. Because the allocator

3:30

may need to scan lists, split blocks, or

3:33

request more memory from the OS. Heap

3:35

allocation is slower and can fragment

3:37

over time. Let's quiz you. Stack or heap

3:40

this time in C++. Take a look here. Is

3:42

this on the stack or the heap? And

3:44

similarly here, is this on the stack or

3:46

the heap? A is on the stack. The

3:48

lifetime is inside fu. New int is on the

3:51

heap. you must explicitly delete it.

3:54

Note the pointer B itself is on the

3:56

stack, but the thing it points to is on

3:58

the heap. So who frees heap memory? In C

4:01

and C++, you free it manually with free

4:04

or delete. If you forget, you create a

4:06

memory leak. Modern languages like Go,

4:09

Java, and Python use garbage collection

4:11

or GC. AGC scans memory, finds reachable

4:15

objects, and deletes those that are no

4:17

longer referenced. This makes

4:18

development easier, but adds overhead.

4:20

GC must pause, mark objects, and

4:23

sometimes compact or reorganize memory

4:25

to reduce fragmentation. But how does

4:27

the compiler decide between stack versus

4:29

heap? At the compiler level, stack

4:31

versus heap placement is determined by a

4:33

combination of lifetime or escape

4:36

analysis, and compile time knowledge of

4:39

type sizes. The compiler always knows

4:41

the exact size and layout of statically

4:44

sized types, primitives, strrus, and

4:47

fixed length arrays. So, it can assign

4:49

each one a precise offset in the stack

4:51

frame. Stack allocation only works

4:53

because sizes and offsets are fully

4:55

known at compile time. However, the

4:57

compiler must also prove through static

4:59

analysis that a value's lifetime is

5:01

confined to the function. Meaning, its

5:04

address doesn't escape. It isn't

5:06

captured by a closure, stored globally,

5:09

returned or shared across threads. If

5:11

the value might outlive the frame even

5:14

though its size is still known, the

5:16

compiler cannot safely place it on the

5:18

stack. In that case, it instructs the

5:20

runtime to allocate the value on the

5:21

heap, which is the only region capable

5:24

of supporting objects with unbounded or

5:26

uncertain lifetimes. Managed languages

5:28

like Go and Java use escape analysis to

5:32

trigger this promotion. Unmanaged

5:33

languages like C or C++ leave the

5:36

decision to the programmer via maloc or

5:38

new. Just as heat memory dynamically

5:40

allocates resources to keep your

5:42

application running smooth on the

5:43

inside, spacelift dynamically manages

5:46

and automates the infra keeping it

5:47

running on the outside. Spacelift is an

5:50

infrastructure as code orchestration

5:52

platform that lets teams provision,

5:54

configure, and govern infrastructure

5:55

across multi cloud environments using

5:58

tools like Terraform, Open Tofu, Anible,

6:00

Kubernetes, Cloud Formation, and Palumi.

6:03

Spacelift connects to your version

6:04

control system, turning infra

6:06

repositories into automated workflows

6:08

with built-in blueprints and guardrails.

6:11

This allows developers to safely self-s

6:13

serve infrastructure in a tool agnostic

6:15

environment, eliminating bottlenecks and

6:17

accelerating delivery. Instead of

6:19

manually applying scripts, spacelift

6:21

automates runs triggered directly by git

6:23

commits and enforces policies written in

6:26

open policy agent before changes ever go

6:28

live. This gives you a unified view of

6:30

infrastructure changes across AWS, GCP,

6:33

and Azure, ensuring that the same

6:35

deployment standards apply everywhere.

6:37

Platform teams can use these features to

6:38

build golden paths for developers to

6:40

self-service infrastructure safely and

6:42

even detect configuration drift

6:44

automatically. And unlike solutions that

6:46

price based on resources under

6:48

management, Spacelift uses concurrency

6:50

based pricing, so you're never penalized

6:52

for modular infra or engineering best

6:54

practices. It's designed for velocity,

6:56

control, and flexibility, especially in

6:58

enterprise environments that need to

6:59

scale infrastructure operations without

7:01

surprise costs. A big thank you to

7:03

Spacelift for sponsoring this video. If

7:05

you want to see how Spacelift can

7:07

streamline your infrastructure

7:08

automation, check them out down below.

7:10

Now, back to the video. Let's talk about

7:12

escape analysis. Why some variables move

7:15

to the heap. Some languages, notably Go

7:18

and Java, use escape analysis to decide

7:20

whether a variable can stay on the stack

7:22

or must go to the heap. If you return a

7:25

reference to a local variable or store

7:27

it somewhere that may outlive its frame,

7:29

the compiler promotes it to the heap. X

7:31

would normally be a stack variable, but

7:34

because its address escapes the

7:35

function, go allocates X on the heap.

7:38

This is exactly the kind of scenario

7:39

that produces heap pressure and GC work.

7:42

But what is the cost of heap memory?

7:44

Heap allocations are slower because they

7:46

involve metadata manipulation, possible

7:49

synchronization between threads, and

7:50

potential garbage collection.

7:52

Additionally, the heap can fragment over

7:54

time. Many small allocations leave

7:56

behind gaps that aren't big enough for

7:58

new requests. Garbage collection helps

8:00

but doesn't eliminate all fragmentation

8:02

patterns. How about multi-threading in

8:04

the heap? If you consider multi-threaded

8:06

programs, heap allocation can actually

8:08

become a bottleneck because threads

8:10

compete for memory. Modern allocators

8:12

avoid global locks by giving each thread

8:14

its own arena or thread local cache.

8:17

Goes runtime for example assigns each P

8:20

or logical processor its own small

8:22

allocator to minimize contention. Let's

8:24

look at this example in Python. Are

8:26

these allocated on the stack or the

8:28

heap? In CPython, all objects live on

8:31

the heap, even integers. The names X and

8:34

Y live in a local namespace, like a mini

8:36

stack, but the actual values live on the

8:38

heap. A subtle low-level detail about

8:41

heap memory that many developers don't

8:43

realize is how the allocator uses

8:45

metadata stored right next to your

8:47

allocated blocks and how that affects

8:49

fragmentation and performance. Most

8:51

maloc implementations carve the heap

8:53

into chunks, and each chunk carries a

8:55

small header that tracks its size,

8:57

whether it's free, and sometimes

8:59

pointers for free list bookkeeping. When

9:01

you free a block, the allocator doesn't

9:03

just mark it as available. It also

9:05

checks the neighboring chunks in memory

9:06

to coales them into a larger free

9:09

region, which reduces fragmentation, but

9:11

can incur extra pointer chasing and

9:13

synchronization if the allocator is

9:15

multi-threaded. This means the pattern

9:17

in which you allocate and free memory

9:19

changes the physical shape of the heap

9:22

over time, affecting everything from

9:23

cache behavior to how often the

9:25

allocator needs to request new pages

9:28

from the OS. Even small changes in

9:30

allocation order or interle allocations

9:32

across threads can lead to surprisingly

9:35

different heap layouts and performance

9:37

characteristics. So an important

9:39

question, why do developers even care

9:41

about heap memory? Even in languages

9:43

with garbage collection, understanding

9:44

heap memory helps you reduce

9:46

allocations, reduce pauses, and increase

9:48

performance. In Go, for example,

9:50

avoiding unnecessary heap allocations

9:52

can dramatically reduce garbage

9:54

collection pressure in a language like

9:55

C++ or Rust. Proper heap usage avoids

9:59

fragmentation and also ensures

10:01

deterministic lifetimes. Again, if you

10:03

want to check out the first ever

10:04

multiplayer MCP-based collaboration

10:06

platform, check out Glue.ai down below.

10:08

And if you want to learn how to build

10:09

Docker, Reddus, and compilers from

10:11

scratch, I highly recommend checking out

10:13

Code Crafters down below.

Interactive Summary

This video provides a deep dive into the concept of heap memory in programming, contrasting it with stack memory. It explains how heap memory is used for dynamic allocation when data sizes or lifetimes are unknown at compile time. The video covers the mechanisms of memory allocators, the role of garbage collection in managed languages, and how compilers perform escape analysis to decide between stack and heap placement. Additionally, it highlights performance considerations like fragmentation and cache locality, while briefly discussing infrastructure automation via Spacelift.

Suggested questions

3 ready-made prompts