Phase 3 — Systems + Concurrency (Months 5–7, W17–W28)¶
“You cannot make ML/data C++ fast, or even correct, until you understand what your code compiles to. This phase is the tax you pay before the interesting work.”
You are twelve weeks into serious C++, you have shipped 150 solutions, and you are now in the phase most self-taught programmers skip. That is why most self-taught systems programmers fail their first onsite: they have memorized std::mutex syntax without understanding the memory model. This phase closes that gap, permanently.
Mission¶
By the end of Phase 3, you can look at a piece of concurrent C++ code and answer three questions without hesitation: (1) does this have a data race? (2) does this actually work on Apple Silicon and other weak-memory ARM hardware, not just x86 in your head? (3) can this deadlock, and if so, when? These are the three questions every real study partner asks after you write your first threaded code sample, and “I would have to think about it” is a failing answer.
You also produce three portfolio artifacts: a cacheline-padded SPSC ring buffer (P3.1), a work-stealing-ish thread pool with future returns (P3.2), and a minimal event loop on kqueue (P3.3). All three are the kind of thing a Zoho / Meta / HFT study partner will actually recognize and ask you about.
The thesis, stated plainly¶
Modern C++ is a systems language pretending to be a general-purpose one. Every abstraction you use (std::mutex, std::shared_ptr, std::vector::push_back) compiles into specific CPU instructions, specific cache-line traffic, and specific interactions with the OS scheduler. If you write concurrent code without understanding this, you will write code that:
Works on x86 (Intel/AMD desktops) and mysteriously fails on ARM (Apple Silicon, mobile, AWS Graviton, most non-x86 servers today).
Passes your tests and races in production.
Uses
mutexfor problems that needatomic, or vice versa.Has 10x throughput headroom sitting unused because of false sharing you cannot see.
The only cure is understanding the C++ memory model well enough that concurrent code stops being magic. That is what file 01 (memory model) exists for. The rest of the phase builds practical tools on that foundation.
Why this matters double for Raghul specifically. You develop on an Apple Silicon Mac (M-series, ARM weak memory ordering). x86-based intuitions produce code that appears to work on your laptop only because Apple’s M1/M2/M3/M4 chips have a hardware-assisted TSO mode for Rosetta emulation — but native ARM binaries run under the weak ordering, and this is exactly where race conditions surface. Reference: Wrenger et al., 2024, Analyzing the memory ordering models of the Apple M1 (Journal of Systems Architecture) — you will read this in file 01.
Exit criteria¶
Explain the C++ memory model to a peer in 20 minutes: happens-before, synchronizes-with, all six memory orders, and one concrete example each of
relaxed,acquire/release,seq_cst.Explain why x86 is “almost sequentially consistent” (TSO) and ARM is not, with one code example that behaves differently.
Recognize and eliminate false sharing given a struct layout.
Write an SPSC lock-free queue from scratch, cacheline-padded, that passes TSan on 1M items.
Write a thread pool with
submit(f) -> future<result>, graceful shutdown, no TSan errors.Write a minimal echo server on
kqueue(Mac) orepoll(Linux) handling 1000 concurrent connections.Recite the top eight causes of undefined behavior and how to detect each with a sanitizer.
Enable and interpret output from ASan, UBSan, TSan, and MSan in a Clang build.
Read a
perfprofile (or Instruments trace on Mac) and identify a cache-miss hotspot.Know when
std::atomicis faster thanstd::mutexand cite the crossover threshold from Fedor Pikus.Explain what
std::mutexactually is on Linux (futex + spin), on Mac (os_unfair_lockunder the hood), on Windows (SRWLock).Write a working DCLP (double-checked locking) using C++11 atomics and explain why the pre-C++11 version was broken.
Explain the ABA problem and when it does / does not matter.
Explain why
std::asyncis considered a mistake by most senior C++ practitioners.Read and follow along with Herb Sutter’s “atomic<> Weapons” talk (2013, still the canonical reference).
If any box is unchecked at W28, extend. Phase 4 (build/test/tooling) is lighter — if you have to steal from any phase to finish Phase 3, steal from Phase 4.
W17–W28 breakdown (12 weeks, overlapping with tail of Phase 2)¶
Overlap with Phase 2 is deliberate. W17–W20 you are still finishing NeetCode; treat systems reading as evening/weekend, not core sessions. From W21 onwards it takes over.
Weeks |
Focus |
Deliverable |
|---|---|---|
W17–W18 |
Reading only. File 01 (memory model) + Herb Sutter’s “atomic<> Weapons” talk (both parts). Take notes. |
Notes committed to Anki / Obsidian |
W19–W20 |
Wrap Phase 2 projects. Meanwhile skim file 02 (threads & sync). |
— |
W21 |
Threads, mutex, condition variables. Small drills. |
Producer-consumer with |
W22 |
Atomics, memory ordering, hands on. Start P3.1 SPSC. |
P3.1 draft |
W23 |
Finish P3.1 with cacheline padding and benchmarks vs Boost. |
P3.1 published |
W24 |
|
file 03 internalized |
W25 |
Thread pool P3.2 build. Correctness + graceful shutdown. |
P3.2 draft |
W26 |
Finish P3.2. Add benchmarks. TSan clean pass required. |
P3.2 published |
W27 |
Linux syscalls (mmap, epoll) + Mac equivalents (kqueue). |
file 05 internalized |
W28 |
Event loop P3.3. Echo server. |
P3.3 published + phase closeout |
What most people get wrong¶
They memorize std::mutex syntax without understanding the memory model. They write:
std::mutex m; int x = 0;
// thread 1: m.lock(); x = 42; m.unlock();
// thread 2: m.lock(); read(x); m.unlock();
…and believe they have understood concurrency. Then they see std::atomic<int> and think “it is a faster mutex,” which is exactly wrong: atomic is lock-free, its semantics are governed by memory orders, and using atomic without picking the right order is worse than using mutex — you get code that looks concurrent, passes basic tests, and races on ARM. This entire phase exists to keep you from being that engineer.
The second common failure: they conflate parallelism (do more with more cores) and concurrency (structure code that waits for I/O). A thread pool is a parallelism tool; an event loop is a concurrency tool. Some workloads want one, some want both, most want neither. You will build both this phase so you know the difference in your fingers.
The third: they trust std::async. std::async in the standard has a launch policy footgun (default launch::async | launch::deferred means “the implementation may or may not spawn a thread”), destructors of futures returned by std::async block (surprise), and there is no cancellation. File 03 covers this in detail. Most senior C++ practitioners have quietly stopped using std::async altogether; you should learn it once and then reach for a real thread pool or a coroutine library.
The 2026 reality check (from research this week)¶
C++20 coroutines are still awkward without a library in 2026. Standard C++ ships the language machinery but no coroutine types. You use them via Boost.Asio (mature; recommended),
cppcoro(Lewis Baker’s, mostly ported to C++20),folly::coro(Facebook, production-grade but heavy dependency), or Boost.Cobalt (newer, promising). File 03 gives an honest walk-through. The 2025 Boost report shows Asio benchmarks ~10% faster with C++20 coroutines than legacyyield_context; the ecosystem is finally catching up.C++ hiring in 2026: senior C++ salaries at $155K–$210K in US, with the strong caveat that the range collapses by ±$80K based on subdomain. HFT / systems / infra pays highest; embedded / defense often lowest. Systems-programming C++ is not going anywhere; the demand is stable, not surging.
Apple Silicon is the ARM you will encounter first. M1/M2/M3/M4 use ARM’s weak memory model natively but have a per-thread TSO mode (used by Rosetta 2). You cannot rely on TSO unless you explicitly enable it via a private prctl-equivalent — native ARM binaries you compile with Xcode’s Clang run under weak ordering. This is why your local tests may pass while production on AWS Graviton fails.
Lock-free is oversold. Fedor Pikus’s CppCon 2016 “Speed of Concurrency” is the reference here (linked in file 04). Lock-free is faster than mutex only under specific contention patterns; for many workloads a good mutex + shorter critical sections beats a hand-rolled lock-free structure. Build one lock-free queue this phase so you know what you are giving up if you never build another.