← All Notes

The Software Stack of Cards

The Analogy

Software is a stack of cards. The smallest cards are at the bottom—closest to the hardware. The largest cards are at the top—the highest abstractions. Each card rests on the one below it.

A small movement at the bottom shifts everything above.

This is what makes low-level code so critical, and why “just change the database” is never simple. The foundation isn’t big and sturdy—it’s small and precise. That’s what makes it powerful. And terrifying.


What Are the Cards?

Each card represents a layer of abstraction over two fundamental resources:

  • Compute — CPU cycles, instructions, work
  • Memory — Allocations, storage, state

Every layer translates your intent into something the layer below can understand. This continues until you hit actual electrons moving through silicon.

Going up = more convenience, more hidden work Going down = more control, more visibility


The Innocent One-Liner

Consider this simple operation:

np.array([1,2,3]) + np.array([1,2,3])

Output: [2, 4, 6]

That innocent + is standing on top of many cards:

  1. Hardware — Transistors flipping, electrons moving, registers and cache
  2. CPU instructions — ADD, MOV, memory access patterns
  3. OS Kernel — Memory allocation, scheduling, syscalls
  4. System libraries — glibc, memory allocators
  5. Language runtime — Python interpreter, garbage collection, GIL
  6. C extensions — numpy’s core doing loops, SIMD optimizations
  7. Python C API — Bridging to the numpy extension
  8. numpy array object — The abstraction you interact with
  9. Your code — The friendly + that “just works”

Each card is a little bigger than the one below. More abstraction, more convenience, more stuff happening—all balanced on that tiny, precise foundation of bits and cycles.


Stacking Stacks

Now imagine that numpy operation running in a typical cloud environment:

np.array([1,2,3]) + np.array([1,2,3])
        ↓
   Python interpreter
        ↓
   numpy C extensions
        ↓
   glibc / memory allocation
        ↓
   Container's isolated filesystem & namespaces
        ↓
   Docker runtime (containerd, runc)
        ↓
   MicroVM kernel (Firecracker, gVisor, etc.)
        ↓
   Hypervisor (KVM, Xen, etc.)
        ↓
   Host OS kernel
        ↓
   Actual hardware

All that coordination, all those context switches, all that abstraction overhead… to compute [2, 4, 6].

Every layer has a cost:

  • Context switches
  • Memory copies
  • Scheduling overhead
  • Coordination code

The industry has traded efficiency for convenience, isolation, and developer velocity. Sometimes that tradeoff makes sense. Sometimes we’re running a simple addition through seventeen layers of indirection because “that’s how we deploy things now.”


Counting Your Cards

What if we chose a different stack?

The same operation in Rust + WASM on Cloudflare Workers:

Rust code → compiled to WASM
        ↓
   V8 isolate (lightweight, no cold start)
        ↓
   Edge node hardware

That’s it. A handful of cards.

  • WASM is already close to metal
  • V8 isolates are tiny and fast
  • No container orchestration
  • No nested virtualization

The work is the same. The path to get there is dramatically shorter.


The Cost of a Card

Every layer has measurable costs:

Cost DimensionWhat It Means
Lines writtenYour code—what you see
Lines executedLibrary code, runtime code, OS code—what actually runs
CPU instructionsThe actual work the processor does
Memory allocatedHeap allocations, stack frames, buffers
SyscallsCrossings between user space and kernel
Context switchesScheduler interruptions, VM boundaries
TimeWall clock duration
Energy / CostCompute → electricity → dollars

Abstraction Budget

Think of abstraction as a budget. Every layer you add spends some of that budget.

The question isn’t “should we use abstractions?”—of course we should. The question is:

For this workload, is this layer worth its cost?

Some examples:

WorkloadGood FitPoor Fit
Stable API, constant trafficContainers, Kubernetes—
Tiny, bursty data pipeline jobsWASM, lightweight runtimesContainers (overhead dominates)
Tight numerical loopsRust, C, PolarsPure Python
Rapid prototypingPython, high-level frameworksLow-level manual memory management

Finding Wins Within Constraints

You don’t always control the whole stack. Team dynamics, company uniformity, existing infrastructure—these matter for reasons beyond pure architecture.

But you can find workarounds.

Example: Polars in a Python shop

df.lazy()
  .filter(...)
  .group_by(...)
  .agg(...)
  .collect()

It looks like Python. But underneath? Rust. Vectorized. No Python GIL strangling your tight loops.

You’ve collapsed part of the stack without fighting the team’s workflow.


The Benchmark Framework (Future Work)

What if we could measure, for any given operation:

Input: 1 line of high-level code

Output:

  • Lines of library code executed
  • Interpreter instructions
  • Syscalls
  • CPU instructions (via perf, etc.)
  • Memory allocated
  • Wall time
  • Estimated cost ($)

Then compare implementations:

ImplementationYour CodeInstructionsSyscallsTimeEst. Cost
numpy + Python + Docker + VM1 line50M20015ms$$$
Polars (lazy) + Python + Docker1 line5M802ms$$
Rust + WASM + edge1 line50K20.1ms$

Same work. Different stack. Wildly different cost.

This gives concrete data for:

  • Teaching — “Here’s what your innocent one-liner actually costs”
  • Decision-making — “Is this abstraction layer worth it for this workload?”
  • Optimization — “Where in the stack is the bloat?”
  • Cost awareness — More compute → more $ → should be a conscious choice

The Key Question

For any piece of software, ask:

How many cards are we going through, and what does each card cost?

Most developers live entirely in the top few cards and have no idea what’s beneath them. Which is fine—until something wobbles at the bottom.

Seeing the whole stack doesn’t mean you have to optimize everything. It means you can make informed tradeoffs instead of accidental ones.


Summary

  • Software is a stack of cards: small at the bottom, large at the top
  • Each card is an abstraction over compute and memory
  • Every layer has a cost in instructions, allocations, time, and money
  • The same work can take wildly different paths through the stack
  • Abstraction is a budget—spend it wisely
  • You don’t need to control the whole stack to make it better
  • The goal: informed tradeoffs, not accidental complexity