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 mutex for problems that need atomic, 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) or epoll (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 perf profile (or Instruments trace on Mac) and identify a cache-miss hotspot.

  • Know when std::atomic is faster than std::mutex and cite the crossover threshold from Fedor Pikus.

  • Explain what std::mutex actually is on Linux (futex + spin), on Mac (os_unfair_lock under 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::async is 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 condition_variable

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

async / future / promise / packaged_task. Skim coroutines.

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. ab benchmark.

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 legacy yield_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.


Nav: ← Phase 2 projects · C++ memory model →