I took seven CS courses between CMU and Georgia Tech that I still think about at work. Not in a nostalgic “ah, college” way. In a “that lab is the reason I caught this bug” way.
Most of what I learned in school I’ve forgotten. That’s fine — it’s how brains work. But a few courses left permanent dents in how I see systems. The kind where you’re debugging a Docker build at 11 PM and you suddenly realize you’re using the same mental model from a lab you did five years ago.
Here’s the list, in roughly the order I’d recommend them to someone who wants to understand how computers actually work — from the metal to the network to the distributed mess on top.
15-213: Introduction to Computer Systems (CSAPP)
This is the one. If you take one systems course in your life, make it this one.
Randal Bryant teaches you how a computer works — not the “here’s a CPU diagram” version, but the version where you understand why your program segfaults, why malloc is slow, why the compiler reorders your instructions, and why that matters.
The labs are legendary. Bomb lab: you get a compiled binary and have to reverse-engineer it by reading x86 assembly to defuse a series of “bombs.” Shell lab: you implement a Unix shell — fork, exec, signals, job control, all of it. Malloc lab: you write your own memory allocator. Proxy lab: you build a concurrent web proxy.
Five years later, I still remember every single one.
Here’s the thing about CSAPP — it doesn’t just teach you systems. It teaches you a way of looking at systems. After malloc lab, you never look at memory the same way. You start seeing allocation patterns everywhere. When I’m staring at a Docker container that’s eating 40GB of disk, some part of my brain is still thinking about memory layout, fragmentation, and the cost of not freeing things you allocated.
The proxy lab is where concurrency clicked for me. Not the textbook “here’s a mutex” version. The version where you have actual clients sending actual requests and things break in ways that are impossible to reproduce. That’s the experience I fall back on every time a CI pipeline has a race condition — you can’t just read the code, you have to think about the interleaving.
15-214: Principles of Software Construction
This is the course that taught me how to write code that other people can work on. Which, it turns out, is a completely different skill from writing code that works.
Java, design patterns, git workflow, JUnit testing. You design a Carcassonne board game from scratch — not just implement it, but design it. UML diagrams, interaction flow diagrams, TA review sessions where they ask “why did you put this interface here?” and you have to defend your decisions.
I hated it at the time. I wanted to write code, not draw diagrams.
Now I’m a DevEx engineer who spends half his time reviewing build system designs and arguing about API boundaries in a monorepo with a hundred packages. The thing I learned in 15-214 — that the structure of your code matters more than the cleverness of your code — is something I use literally every day.
Design patterns get a bad rap because people over-apply them. But knowing when a factory pattern is load-bearing versus when it’s ceremony? That’s the course. Knowing when to put a seam in your code so it can evolve without breaking everything downstream? That’s the course. The TA review sessions were basically code reviews before I knew code reviews existed.
15-441: Computer Networks
Justine Sherry, C language, and three labs that make you build the internet from scratch.
Lab one: a high-concurrency web server with HTTPS support. Lab two: a TCP implementation — sliding windows, retransmission, congestion control, the whole stack. Lab three: an adaptive video streaming CDN.
The networking knowledge itself has been useful in predictable ways — I debug DNS issues in our CI pipeline, I understand why ACR registry replication has latency, I know what’s happening when a curl to an Azure endpoint times out.
But the real value was writing a TCP stack. When you implement reliable delivery on top of unreliable packets, something clicks about what “reliability” actually costs. Every retry, every timeout, every acknowledgment is a tradeoff between latency and correctness. That mental model shows up every time I design a retry mechanism for a flaky service — which, in CI/CD infrastructure, is approximately every week.
The video CDN lab taught me about adaptive systems — systems that observe their environment and adjust. That’s basically what autoscaling is. That’s what cache eviction policies are. The specific protocol (DASH) doesn’t matter. The pattern — measure, adapt, measure again — is everywhere.
15-746: Storage Systems
Greg Ganger’s course, and it goes deep. From the physics of spinning disks to the architecture of BigTable.
The labs were wild. You build an SSD management system — wear leveling, garbage collection, the whole flash translation layer. Then you build a cloud file system (think Dropbox) with snapshots, deduplication, and multi-tier caching.
This course is the reason I think about storage as a system rather than a black box. When someone says “just write it to disk,” I hear a chain of decisions: which disk? What’s the write amplification? What happens when the cache is cold? What’s the consistency model?
The deduplication lab connects directly to Docker layer caching. A Docker image is, fundamentally, a content-addressed storage system with deduplication. When I’m optimizing our image build pipeline — reordering layers so that the least-changing ones are at the bottom, splitting uv sync into external deps and workspace packages so they cache independently — I’m using the same mental model from that cloud file system lab. Different scale, same principle: don’t store the same bytes twice, and put the volatile stuff where it won’t invalidate the stable stuff.
The caching tier design also shows up in Bazel. Bazel’s cache hierarchy — action cache, content-addressable store, repository cache, remote cache — is a multi-tier caching system with different eviction policies and consistency guarantees at each level. I didn’t know that’s what I’d be working on. But the course gave me the vocabulary to understand it immediately when I saw it.
15-640: Distributed Systems
Mahadev Satyanarayanan’s version. Satya — the CMU one, not the Microsoft one — is one of those professors who makes you feel like distributed systems is the most important field in computer science. He might be right.
Four labs, each one a different slice of the problem. Lab one: RPC in C — you marshal and unmarshal data by hand, no protobuf, no gRPC, just raw bytes and a prayer. Lab two: a remote file system in Java with caching and consistency. Lab three: an auto-scaling system with a queue, frontend workers, and backend services. Lab four: two-phase commit.
The auto-scaling lab is the one I think about most. You have a queue filling up with requests, a set of frontend instances serving them, and backend instances doing the actual compute. You have to write the logic that decides when to scale up, when to scale down, and how to handle the lag between “I requested a new instance” and “the instance is ready.”
That’s exactly the problem our CI infrastructure solves — we run auto-scaling GitHub runners that spin up when pipeline load increases and spin down when it’s quiet. The specific technology is different (Kubernetes pods instead of lab VMs), but the problem is identical. When do you scale? How do you handle the cold-start penalty? What do you do when demand drops faster than you can deallocate?
Two-phase commit taught me something more subtle: that distributed consensus is expensive, and most systems avoid it by relaxing their consistency guarantees. Every time I see an eventually-consistent cache, or a build system that tolerates stale data, or a CI pipeline that retries instead of coordinating — that’s the lesson. Perfect consistency is possible. It’s just usually not worth the cost.
CS 6200: Introduction to Operating Systems (Georgia Tech)
Ada’s course at GaTech. Multi-threaded client/server architecture, IPC, gRPC.
This was the course where concurrency stopped being theoretical and started being mechanical. You don’t just learn about threads — you build systems where threads are the load-bearing structure. A multi-threaded server that has to handle concurrent clients without corrupting shared state. IPC between processes that need to cooperate without blocking each other.
The gRPC project was ahead of its time — or maybe I just didn’t realize at the time how much of modern infrastructure runs on RPC frameworks. Every microservice, every CI worker talking to a coordinator, every build system distributing work to remote executors — it’s all RPC under the hood. Having built one from scratch means I’m never confused about what’s actually happening when a gRPC call fails. It’s a socket. It’s serialization. It’s a thread on the other end. Demystifying that is worth more than any amount of documentation.
CSE 6220: High Performance Computing (Georgia Tech)
Parallel computing in C. MPI scatter/gather, CUDA kernels, the works.
This is the course that taught me to think about parallelism as a design constraint, not an optimization. When you’re writing CUDA kernels, you don’t start with a serial algorithm and “parallelize” it. You start with the parallel structure and figure out how to map your problem onto it. Warps, blocks, shared memory, coalesced access patterns — the hardware dictates the algorithm.
I don’t write CUDA code in my day job. But the mental model matters more than the API.
When I’m looking at a CI pipeline that takes 40 minutes, I’m instinctively looking for the critical path. Which jobs are sequential that could be parallel? Which stages are blocked on a shared resource? Where’s the synchronization point that’s serializing everything?
That’s scatter/gather thinking. Break the work into independent chunks, distribute them, collect the results. Our Bazel build does exactly this — test targets get sharded across runners, each runner works independently, results get collected at the end. The CI pipeline that builds Docker images for multiple architectures in parallel? Scatter/gather. The concurrent build of independent CUDA libraries that Docker forces to be sequential because of layer ordering? That’s a parallelism bug, and I only see it as a bug because of this course.
The compound effect
None of these courses taught me Bazel. None of them taught me Docker, or CI/CD, or Kubernetes, or any of the specific tools I use daily. The tools change. The last five years have proven that — half the technologies I use now didn’t exist when I was in school.
What didn’t change is the mental models.
Memory layout from CSAPP shows up in container optimization. Storage system design from 15-746 shows up in cache hierarchies. Distributed consensus from 15-640 shows up in build system consistency. Parallel decomposition from CSE 6220 shows up in CI pipeline design. Software architecture from 15-214 shows up in literally every code review.
The courses that stuck weren’t the ones with the best lectures. They were the ones with the best labs — the ones where you built something hard enough that the knowledge got welded into your brain through frustration and late nights and the dopamine hit of finally getting it to work.
That’s the thing about systems education. You don’t learn systems by reading about them. You learn them by building them, breaking them, and debugging them at 3 AM when nothing makes sense. The understanding that survives that process is the understanding that sticks.