CS FUNDAMENTALS — BUILD IT TO UNDERSTAND IT
6 TIERS 25 PROJECTS C · C++ · GO ALL PUBLIC, ALL FREE

SYSTEMS
I NEVER
HAD TO
BUILD

Why this list

Most of us use an OS, a network stack, a database, and a distributed store every single day without ever building one. These six courses are the internet's go-to answer for "how do I actually understand systems" — not by reading about them, but by writing an OS kernel, a TCP stack, a database engine, and a Raft cluster from scratch.

Go top to bottom for the full sequence, or jump straight to the tier you need. Every course is taught at a top CS program and every lab is public and free — no accounts, no paywalls.

01 Operating Systems
02 Networking
03 Databases
04 Parallel & GPU
05 Distributed Sys.
06 More reps
01
MIT 6.1810 — Operating System Engineering (fka 6.828 / 6.S081) · xv6, in C
9 labs
01.01
Unix utilities & syscalls
xargs, find · trace, sysinfo
01.02
Page tables & traps
virtual memory · interrupts
01.03
Copy-on-write & locks
lazy fork · parallel kernel
01.04
File system, mmap & net
large files · symlinks · NIC driver
01.01 — Unix utilities & syscalls
Utilities & Syscalls

You start by writing small Unix tools (a Xargs, a Find, a ping-pong pipe program) directly against xv6's syscall interface, then add two new syscalls yourself — a syscall tracer and a sysinfo call that reports kernel state. This is the on-ramp: it forces you to read the xv6 source and understand exactly how a process asks the kernel to do something, before you touch anything that runs in kernel mode.

Language: CRISC-VQEMU
01.02 — Page tables & traps
Page Tables & Traps

You print a process's page table, then add a second, larger address space per process to speed up syscalls, before moving into how xv6 handles traps — the mechanism that switches from user mode to kernel mode on a syscall, interrupt, or exception. By the end you understand exactly what "the CPU jumps into the kernel" means at the level of RISC-V registers, the trampoline page, and the trapframe.

Language: CVirtual memory
01.03 — Copy-on-write & locks
Copy-on-Write & Locks

fork() in xv6 naively copies the entire parent address space — you rewrite it to share physical pages between parent and child and only copy-on-write when either one writes, with a reference count to avoid freeing memory too early. Then you go multiprocessor: replace xv6's coarse kernel lock with fine-grained locks in the memory allocator and the buffer cache, and watch throughput actually scale with cores instead of serializing on one giant lock.

Language: CSMPRace conditions
01.04 — File system, mmap & networking
File System, mmap & Networking

You extend xv6's on-disk file system to support larger files and symbolic links, then implement mmap() to map files directly into a process's address space with lazy, page-fault-driven loading. The last lab hands you a real NIC (the E1000) and has you write the driver's transmit and receive paths — so the semester ends with your kernel talking to the outside world, not just to itself.

Language: CDisk layoutE1000 NIC
02
Stanford CS144 — Intro to Computer Networking build a TCP/IP stack, in C++
7 checkpoints
02.01
Byte stream & reassembly
in-memory stream · out-of-order bytes
02.02
TCP receiver
sequence numbers · sliding window
02.03
TCP sender
retransmission · congestion control
02.04
Network interface & router
ARP, Ethernet · IP forwarding
02.01 — Byte stream & reassembly
Byte Stream & Reassembly

Checkpoint 0 has you write a tiny web crawler against the kernel's own TCP socket so you've felt what the abstraction you're about to build actually does, then implement an in-memory reliable byte stream. Checkpoint 1 is the Reassembler: network segments arrive out of order and can overlap or duplicate, and your job is to stitch arbitrary incoming byte ranges back into one ordered stream — the unglamorous piece that makes everything after it possible.

Language: C++20Framework: Minnow
02.02 — TCP receiver
TCP Receiver

You implement the receiving half of TCP: turning incoming segments into calls on your Reassembler, tracking the 32-bit sequence-number space (with wraparound and the initial-sequence-number offset), and reporting a correct window size back to the sender so it knows how much more it's allowed to send. This is where TCP's "reliable stream over an unreliable network" promise actually gets kept — and where the classic off-by-one sequence number bugs live.

Language: C++20Interops with real TCP
02.03 — TCP sender
TCP Sender

The sender side has to decide when to send, what to send, and when to give up waiting and retransmit — implemented with a retransmission timer that doubles on every consecutive timeout (exponential backoff) and resets once an ACK makes progress. Put your receiver and sender together and you have a working, RFC-compliant TCP that can hold a real connection with an actual server on the internet — the moment this course is built around.

Language: C++20RTO / backoff
02.04 — Network interface & router
Network Interface & Router

The last checkpoints drop down a layer: a NetworkInterface that translates between IP datagrams and Ethernet frames using ARP (with a cache and retransmission of pending requests), and then an IP router that holds a forwarding table and picks the correct outgoing interface by longest-prefix match. Finish this and you've personally implemented every layer between "a program calls send()" and "a frame goes out on the wire."

Language: C++20ARP · Ethernet · IP
03
CMU 15-445/645 — Database Systems BusTub, in C++
5 projects
03.01
C++ primer
trie · RAII · smart pointers
03.02
Buffer pool manager
disk ↔ memory · LRU-K eviction
03.03
B+Tree index
range scans · concurrent latching
03.04
Query execution & concurrency
operators, joins · 2PL / MVCC
03.01 — C++ primer
C++ Primer

Project 0 is a copy-on-write trie built with a strict set of C++ rules — no raw pointers, no locks, only shared_ptr and immutability — that must be completed with a perfect score before you're allowed to touch BusTub itself, the teaching relational database the CMU Database Group built specifically for this course. It exists purely to filter out "I don't actually know modern C++" before it costs you a week on Project 1.

Language: C++17Gate: must be 100%
03.02 — Buffer pool manager
Buffer Pool Manager

Every database is bigger than RAM, so it needs a component that shuttles fixed-size pages between disk and an in-memory pool, tracks which pages are pinned (in use) vs. evictable, and picks a victim to evict using an LRU-K replacement policy when the pool is full. Get this wrong and every layer built on top of it — the index, the query executor — silently corrupts data or deadlocks, which is exactly why it's project 1.

Language: C++17LRU-KThread-safe
03.03 — B+Tree index
B+Tree Index

You implement a disk-based, thread-safe B+Tree — the structure almost every relational database uses for its indexes — supporting point lookups, range scans via leaf-node linked lists, and concurrent inserts/deletes using latch crabbing so multiple threads can safely walk and modify the tree at once. It's the project most students say taught them more about real data structures than an entire algorithms course did.

Language: C++17Latch crabbing
03.04 — Query execution & concurrency control
Query Execution & Concurrency

Project 3 has you build the Volcano-style iterator operators (sequential scan, joins, aggregation, sorting) that turn a query plan into actual results, one tuple at a time. Project 4 then makes it correct under concurrent transactions — implementing two-phase locking or multi-version concurrency control so overlapping readers and writers can't corrupt each other's view of the data. Finish both and you have a real, working relational database engine you wrote yourself.

Language: C++172PL / MVCC
04
CMU 15-418/618 & Stanford CS149 — Parallel Computing C++, ISPC, CUDA, OpenMP/MPI
4–5 assignments
04.01
Multicore & SIMD
ISPC · threads · speedup analysis
04.02
Parallel task scheduler
thread pool · work stealing, from scratch
04.03
GPU programming
CUDA renderer · memory hierarchy
04.04
Large-scale parallel workloads
graph algorithms · OpenMP / MPI
04.01 — Multicore & SIMD
Multicore & SIMD

Assignment 1 starts you on a single machine: you parallelize small programs (a Mandelbrot renderer, a sqrt approximator) first with ISPC, which compiles a single-program description down to SIMD instructions across the cores you have, then with raw threads — and you're graded partly on producing a speedup analysis, not just correct output. It's the fastest way to build real intuition for why "more cores" doesn't automatically mean "more speed."

ISPCpthreadsAmdahl's law
04.02 — Parallel task scheduler
Parallel Task Scheduler

You build a task execution library from scratch in C++ — the same category of system as a thread pool inside a game engine or a build system — implementing several increasingly sophisticated scheduling policies for task graphs with dependencies, and measuring how scheduling overhead and load balancing trade off against each other as task sizes shrink. This is where "parallel programming" stops being about calling a library and starts being about building one.

Language: C++Task graphs
04.03 — GPU programming
GPU Programming

You write a parallel renderer in CUDA — circles composited onto a canvas — which sounds like graphics but is really a forcing function to understand thread blocks, warps, shared memory, and memory coalescing, because a naive implementation is both wrong (race conditions on overlapping pixels) and slow (uncoalesced global memory access) at the same time. This is most people's first real contact with how a GPU actually schedules and moves data, underneath the CUDA/PyTorch abstractions used daily in ML.

CUDAWarps · shared mem
04.04 — Large-scale parallel workloads
Large-Scale Parallel Workloads

The final assignment moves to distributed-memory parallelism: graph algorithms like PageRank or BFS parallelized first with OpenMP on one machine, then with MPI across multiple machines that share nothing and must communicate explicitly by message-passing. It's a deliberate bridge — the programming model here (independent nodes, explicit messages, no shared state) is exactly what tier 05's distributed systems course is built on.

OpenMPMPI
05
MIT 6.5840 — Distributed Systems (fka 6.824) · in Go
5 labs
05.01
MapReduce
distributed batch processing
05.02
Raft consensus
leader election · log replication
05.03
Fault-tolerant KV store
linearizability on top of Raft
05.04
Sharded KV service
dynamic reconfiguration · shard migration
05.01 — MapReduce
MapReduce

Lab 1 has you implement a MapReduce coordinator and worker pool from the original Google paper: the coordinator hands out map and reduce tasks over RPC, workers process them and write intermediate files, and — the actual point of the lab — the coordinator has to notice when a worker dies mid-task and re-assign that task without corrupting output. It's the gentlest possible introduction to the core distributed-systems problem: things fail, and your system has to keep working anyway.

Language: GoRPCWorker failure
05.02 — Raft consensus
Raft Consensus

Lab 3 is the heart of the course: implement Raft, the consensus algorithm behind etcd, CockroachDB, and Consul, across three stages — leader election with randomized timeouts, log replication with the AppendEntries protocol, and persistence plus log compaction via snapshots so a restarted server can recover state without replaying its entire history. Tests inject network partitions, dropped RPCs, and server crashes at every stage, so "it passes on my laptop" and "it's actually correct" are very different bars here.

Language: GoThe famously hard lab
05.03 — Fault-tolerant KV store
Fault-Tolerant KV Store

With Raft working, Lab 4 puts a key/value service on top of it: clients send Put/Append/Get RPCs to whichever server they believe is leader, the service submits each operation to Raft, and only applies it — and replies to the client — once Raft confirms it's committed on a majority of replicas. You also have to handle duplicate client requests from retries without applying them twice, which is where "distributed systems are hard" stops being an abstraction and starts being a specific bug you're chasing.

Language: GoLinearizability
05.04 — Sharded KV service
Sharded KV Service

The final lab splits the keyspace across multiple independent Raft groups, each owning a subset of shards, coordinated by a separate shard controller that can move shard ownership between groups while the system stays live. The hard part isn't sharding itself — it's migrating a shard from one Raft group to another without ever losing a write or serving a request against stale data during the handoff, which is essentially how real systems like CockroachDB or DynamoDB scale writes horizontally.

Language: GoMulti-Raft
06
More in the same genre once tiers 01–05 aren't enough
3 courses
06.01
Berkeley CS186
Database Systems · a second, different DB build
06.02
UW CSE 452
Distributed Systems · another take on 6.5840's core idea
06.03
Brown CS 1380
Distributed Systems · build a distributed store, differently
06.01 — Berkeley CS186
Berkeley CS186

Berkeley's database course, structurally close to 15-445 but its own codebase (SimpleDB) and its own opinions on how to teach storage, indexing, query optimization, transactions, and recovery — worth doing after 15-445 specifically because a second implementation of the same ideas in a different codebase is what makes the concepts (not just "how BusTub happens to do it") actually stick.

SimpleDBRecovery & optimization
06.02 — UW CSE 452
UW CSE 452

A distributed systems course whose main project — a highly-available, fault-tolerant, transactional key-value store, built in Java over several team labs — was explicitly designed as a sibling to MIT's 6.824/6.5840 project. Same core problem, different language, different framing of the same consensus-and-replication ideas from tier 05.

Language: JavaTeam labs
06.03 — Brown CS 1380
Brown CS 1380

Brown's distributed systems course builds up a distributed computing framework across seven milestones — centralized computing, serialization, actors & RPC, node groups & gossip, distributed storage, distributed processing, and cloud deployment — ending with the same destination as 6.5840 (a working distributed store you built) reached through a very different, more incremental path.

Milestones M0–M6
How the tiers actually connect
2 threads
01
One fast, correct machine
Tiers 01–04
OS (how a single machine runs code) → Networking (how machines talk) → Databases (how to store and query data reliably) → Parallel/GPU (how to make all of the above fast on modern hardware). Each one is a self-contained systems course you can do in any order once you have C/C++ and a Linux terminal.
02
Many machines, one system
Tier 05 — and beyond
6.5840 is where OS-level failure handling, network-level RPC, and database-level correctness under concurrency all get composed into one problem: keep a service correct when the machines running it can crash, and the network can drop, delay, or reorder messages at any time. Tier 06 is just more reps at that same problem, in different languages and codebases.