coroutines
dynamic allocation
stackless programming
computer science
programming concepts

Why stackless coroutines require dynamic allocation?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Stackless coroutines do not preserve a full private call stack when they suspend. Instead, they preserve only the state that must survive across suspension points. That state has to live somewhere with a lifetime longer than the current function call, which is why stackless coroutine implementations often use dynamically allocated coroutine frames.

Stackless Versus Stackful Coroutines

A stackful coroutine behaves more like a lightweight thread. It can suspend deep inside nested calls because it owns its own stack.

A stackless coroutine works differently. It can suspend only at specific points the compiler or runtime knows about, and the state that matters at those points is stored explicitly.

That stored state usually includes:

  • the current resume position
  • local variables still needed later
  • pending temporaries that cross a suspension point
  • handles or references the coroutine must keep alive

So the coroutine becomes a state machine plus a frame object.

Why the Normal Stack Is Not Enough

An ordinary stack frame disappears when the function returns. But a suspended coroutine has not really finished. It needs to pause now and resume later, possibly after its caller has already returned.

That creates the core requirement:

  • coroutine state must outlive the current stack frame

If the live state stayed only on the regular stack, suspension would be impossible once the surrounding call returned. The state must therefore move into storage with a longer lifetime.

Why Dynamic Allocation Is Common

The easiest way to give that state an independent lifetime is to allocate a coroutine frame dynamically.

Conceptually, the runtime stores something like this:

text
1CoroutineFrame
2  state = 3
3  local_total = 42
4  local_index = 7

When the coroutine resumes, the runtime looks at state and jumps back into the corresponding part of the generated state machine.

Dynamic allocation is common because the runtime often does not know at compile time exactly how long the coroutine object will live or who will resume it later.

Not an Absolute Rule

A more precise statement is that stackless coroutines often require dynamically allocated state, not that heap allocation is logically mandatory in every possible implementation.

Compilers can sometimes optimize the frame away or place it in non-heap storage if they prove that:

  • the coroutine never escapes a local scope
  • its lifetime is tightly bounded
  • no general resumption handle is needed

But those are optimizations. The general resumable model still needs coroutine state with a lifetime independent of the caller's stack.

A Familiar Example: Generators

Python generators are a good intuitive example of stackless-coroutine-style behavior.

python
1def running_total():
2    total = 0
3    while True:
4        value = yield total
5        if value is None:
6            value = 1
7        total += value
8
9g = running_total()
10print(next(g))
11print(g.send(3))
12print(g.send(5))

The generator keeps total and its resume point alive across each yield. That persistence works because the generator's execution state is stored in an object, not left in an ordinary transient stack frame.

The Real Tradeoff

Stackless coroutines trade flexible suspension for a smaller preserved state.

  • stackful coroutine: save an entire stack
  • stackless coroutine: save only the state that crosses suspension points

That can reduce memory usage per coroutine, but it pushes more work onto the compiler or runtime to capture and store exactly the right state.

Common Pitfalls

A common mistake is saying stackless coroutines need dynamic allocation simply because they have “no stack.” The real reason is state lifetime, not the absence of a dedicated stack alone.

Another mistake is assuming heap allocation is always unavoidable. Optimizing compilers can sometimes eliminate it in constrained cases.

A third issue is ignoring the upside. Dynamic coroutine frames are not just overhead; they are what make cheap suspend-and-resume behavior possible without a full stack per coroutine.

Summary

  • Stackless coroutines preserve explicit suspension state instead of a whole stack
  • That state must outlive the current stack frame when the coroutine suspends
  • Dynamically allocated coroutine frames are the common solution
  • Some implementations can optimize allocation away in limited cases
  • The core issue is independent state lifetime, not just “no stack” as a slogan

Course illustration
Course illustration

All Rights Reserved.