Five languages, five projects, one question asked five times. This page covers the whole curriculum plan: how it's delivered, prerequisites, recommended order, difficulty, and the full milestone syllabus for every course. Each course's actual material — installation, language crash course, and all twelve milestones — lives in its own instalment, linked at the bottom of this page.
| Go | Digital Ant Colony. Thousands of independent agents, channels, failure injection, eventually a small distributed system. |
| Ruby | Automation DSL. A pipeline language built out of blocks and metaprogramming that inspects and rewrites itself. |
| Perl | Text Archaeologist. Ingest ugly heterogeneous data, extract entities, correlate events, build a searchable graph. |
| Erlang | The Internet That Never Dies. Thousands of supervised processes that crash, restart, partition, and recover. |
| Racket | Language Factory. A toolkit for defining small languages, ending in real #lang implementations. |
From here, each course starts with its own instalment: what we're building, installation and first program, then the language crash course, before the twelve milestones begin.
Written in full, these five courses are the size of a book. Delivering them as one document would mean losing exactly the parts that make them worth reading: the line-by-line explanations, the exercises, the debugging sections. So each course is split into instalments — this curriculum map, then five self-contained documents per course, each readable on its own. Every instalment ends with a button to the next one, so you can move through a whole course, or the whole curriculum, by clicking forward.
The instalment sequence, and how far each course currently reaches:
| Course | Instalments | Status |
|---|---|---|
| Go — Digital Ant Colony | Parts 0–2, Milestones 1–4, 5–8, 9–12, advanced phase & final challenge | written |
| Ruby — Programmable Automation DSL | Parts 0–2, Milestones 1–4, 5–8, 9–12, advanced phase & final challenge | written |
| Perl — Text Archaeologist | Parts 0–2, Milestones 1–4, 5–8, 9–12, advanced phase & final challenge | written |
| Erlang — The Internet That Never Dies | Parts 0–2, Milestones 1–4, 5–8, 9–12, advanced phase & final challenge | written |
| Racket — Language Factory | Parts 0–2, Milestones 1–4, 5–8, 9–12, advanced phase & final challenge | written |
Instalments are numbered continuously across the whole curriculum — each one says which number it is, and what's next, in a footer at the bottom. If you'd rather reorder the courses themselves — for example, taking Erlang immediately after Go so the two concurrency models sit side by side — nothing here enforces the numbered order; jump straight to that course's instalment from the links at the end of this page.
Each course is built around one question that the language answers unusually well. The project exists to force you into that answer.
| Course | The question the language answers | What the project forces you to confront |
|---|---|---|
| Go | How do I write a program with tens of thousands of independent activities without drowning in locks? | Shared mutable state between concurrent agents, and the discipline of passing ownership through channels instead. |
| Ruby | How do I make a configuration file executable, readable, and extensible by its users? | The line between "data describing work" and "code performing work", and how blocks plus metaprogramming erase it. |
| Perl | How do I turn a pile of inconsistent, half-broken text into structured facts, fast, on the command line? | Real-world data that violates every assumption your parser makes, at a scale where you cannot load it into memory. |
| Erlang | How do I build something that keeps working while parts of it are broken? | Failure as a normal, expected, routinely-exercised code path rather than an exception to be prevented. |
| Racket | How do I build the language the problem wants, instead of encoding the problem in the language I have? | The compile-time/run-time boundary, and the fact that syntax is data you can compute with. |
There is a deliberate pairing structure. Go and Erlang are both about concurrency and disagree profoundly about how to get it. Ruby and Racket are both about growing a language toward the problem and disagree about whether you do that at run time or compile time. Perl stands alone as the Unix-shaped view of computing: a program is a filter, text is the universal interface.
cd, ls, pipes, redirection, environment variables, editing PATH. The Perl course leans on this heavily; the others assume it.Any prior exposure to these five languages, functional programming, actor models, compiler theory, or distributed systems. Every term gets defined when it first appears. Where a concept is genuinely hard (hygiene in macros, split-brain in distributed systems) it gets its own section rather than a parenthesis.
Roughly 180–260 hours for all five if you do the exercises rather than reading past them. At six hours a week that is about a year. At fifteen it is about four months. Doing one course properly beats skimming five.
The courses are numbered as you listed them, but the order I recommend studying them in is different:
Go ──────────► Erlang ──────────► Ruby ──────────► Racket
(1) (4) (2) (5)
│
└── Perl (3) fits anywhere; it shares no concepts with the others
The reasoning:
If you would rather work strictly in the numbered order, nothing breaks. The Ruby-then-Racket connection is the only one I would try to preserve.
Four different kinds of difficulty, rated 1 (easy) to 5 (hard), because they are not the same thing. A language can have easy syntax and a brutal conceptual model, or the reverse.
| Course | Syntax | Tooling | Concepts | Debugging | Hours |
|---|---|---|---|---|---|
| Go | 2 | 1 | 3 | 3 | 35–50 |
| Ruby | 2 | 2 | 3 | 2 | 30–45 |
| Perl | 4 | 3 | 2 | 3 | 30–45 |
| Erlang | 4 | 3 | 5 | 4 | 45–65 |
| Racket | 3 | 2 | 5 | 3 | 40–60 |
Notes on the ratings. Go's concept score is entirely concurrency; the rest of the language is deliberately small. Perl's syntax score is high because of sigils and context, a rule that has no equivalent in most languages, and its debugging score is high because a typo can be silently valid. Erlang is hard on every axis at once: unfamiliar syntax, an unfamiliar build tool, and a model that asks you to unlearn defensive programming. Racket's syntax is trivial to read after an hour, and its concept score comes entirely from macros and phase separation.
#lang.Each course follows the same shape: Part 0 (what we are building), Part 1 (install and first program), Part 2 (language crash course), then twelve milestones, then an advanced phase, a final challenge, and a knowledge check. Below is the milestone plan for each.
| # | Milestone | Concepts introduced |
|---|---|---|
| 1 | One ant on a grid, ticking | structs, methods, pointers, slices, CLI flags, package layout |
| 2 | A thousand ants, still sequential | maps, deterministic randomness, table tests, benchmarks as a baseline |
| 3 | Behaviour: forage, carry, return | interfaces, state machines, errors and errors.Is |
| 4 | Each ant becomes a goroutine | goroutines, WaitGroup, the first real data race, go test -race, mutexes |
| 5 | Replace locks with channels | channels, select, the world as a single owning goroutine, request/reply |
| 6 | Pheromones and evaporation | tickers, fan-in, buffered channels, time in simulations |
| 7 | Graceful shutdown | context, signal handling, errgroup, goroutine-leak tests |
| 8 | Chaos: crashes, drops, slow ants | panic/recover, supervisor goroutines, timeouts, fault injection as config |
| 9 | Metrics and introspection | atomics, expvar, an HTTP endpoint, net/http/pprof |
| 10 | Live visualisation | terminal rendering, a web view over server-sent events, decoupling render from simulation |
| 11 | Backpressure and performance | worker pools, bounded queues, load shedding, escape analysis, allocation profiles |
| 12 | Multiple colony nodes over the network | encoding, TCP servers, reconnection, partitions, chaos tests across processes |
| # | Milestone | Concepts introduced |
|---|---|---|
| 1 | A gem skeleton and a Step object | objects, methods, bundler, project layout, irb |
| 2 | pipeline "x" do ... end that runs | blocks, yield, procs and lambdas, the builder pattern |
| 3 | Bare keywords inside the block | instance_eval, self, scope gates, the cost of a clean DSL |
| 4 | Build an AST instead of executing | value objects, immutability by convention, separating description from execution |
| 5 | The execution engine | the interpreter pattern, a context object, step results, logging |
| 6 | Failure handling: when_failed, retries | exception classes, ensure, retry, designing recoverable steps |
| 7 | Steps registered dynamically | define_method, method_missing, respond_to_missing?, registries |
| 8 | Steps that do real work | HTTP, file and SQLite outputs, pluggable summarisers, configuration |
| 9 | Testing a DSL | RSpec, doubles, a test DSL for the DSL, contract tests for plugins |
| 10 | Self-inspection | reflection, to_h, dry runs, graph rendering, instrumentation via module prepend |
| 11 | Pipelines that rewrite themselves | AST transformation at run time, middleware, hooks, safety limits |
| 12 | Ship it as a gem | gemspec, versioning, a CLI, YARD docs, publishing |
| # | Milestone | Concepts introduced |
|---|---|---|
| 1 | A filter that reads standard input | scalars, strict/warnings, perldoc, the diamond operator |
| 2 | Counting and summarising | arrays, hashes, context (the rule with no equivalent elsewhere), sorting |
| 3 | Parsing Apache and nginx logs | regexes, named captures, anchors, greediness, //x readable patterns |
| 4 | A record model | references, nested data, Data::Dumper, the arrow rule |
| 5 | Splitting into modules | packages, lib/, use, cpanm, local::lib, dependency management |
| 6 | Pluggable format readers | CSV, JSON and XML modules, dispatch tables, sniffing formats |
| 7 | Streaming a 10 GB file | line-by-line processing, encodings, memory profiling, malformed-input policy |
| 8 | Entity extraction and normalisation | IPs, timestamps, paths, identifiers; canonical forms; time zones |
| 9 | Correlation and sessionisation | time windows, joins, SQLite via DBI, indexes |
| 10 | A real command-line tool | Getopt::Long, exit codes, signals, pipes, --help, composability |
| 11 | Testing and profiling | Test2, prove, fuzzing with corrupted input, Devel::NYTProf, optimisation |
| 12 | The knowledge graph | graph storage, queries, forking for parallelism, plugin architecture, packaging |
| # | Milestone | Concepts introduced |
|---|---|---|
| 1 | The shell and a first module | erl, expressions, atoms, compiling, rebar3 |
| 2 | Functions over data | pattern matching, immutability, lists/tuples/maps, recursion, tail calls, guards |
| 3 | A node as a raw process | spawn, !, receive, mailboxes, selective receive |
| 4 | Let it crash | links, monitors, exit signals, trap_exit, why defensive code is discouraged |
| 5 | The node as a gen_server | OTP behaviours, call vs cast, state, timeouts |
| 6 | Supervision trees | restart strategies, intensity limits, dynamic children, startup ordering |
| 7 | A network of a thousand nodes | registries, ETS, process groups, routing, message loss |
| 8 | Chaos injection | random crashes, slow handlers, malformed messages, memory pressure |
| 9 | Observability | counters, observer, recon, tracing a live system safely |
| 10 | The live dashboard | an HTTP server in Erlang, streaming updates, aggregation without slowing the system |
| 11 | Actually distributed | multiple BEAM nodes, cookies, net_kernel, partitions, split brain |
| 12 | Releases and property tests | EUnit, Common Test, PropEr, relx releases, upgrades |
| # | Milestone | Concepts introduced |
|---|---|---|
| 1 | Racket, DrRacket, and raco | s-expressions, definitions, modules, the REPL |
| 2 | Functional groundwork | lists, pairs, higher-order functions, recursion, match |
| 3 | Structs and contracts | struct, contracts as executable specifications, error messages |
| 4 | An interpreter for a config language | AST design, environments, evaluation, quote vs data |
| 5 | First macros | define-syntax-rule, hygiene demonstrated by breaking it |
| 6 | Real macros | syntax-parse, syntax classes, compile-time errors with source locations |
| 7 | The finance DSL | validation passes, a small type checker, phase separation |
| 8 | The toolkit itself | generating parsers, AST types and checkers from a specification |
| 9 | Your own #lang | readers, module languages, #lang finance files that run |
| 10 | The robot DSL | effects, sequencing, a stepper, interpreters as libraries |
| 11 | Compiling instead of interpreting | macro-based compilation, benchmarks against the interpreter |
| 12 | Tooling and the game DSL | rackunit, editor integration, packaging, the capstone language |
That's the whole curriculum. Go is first, so its instalment picks up next: what we're building (an ant colony that finds itself corrupting its own state the moment it goes concurrent, and the idiom that fixes it), installing the toolchain, and a full language crash course, before Milestone 1 begins.