rgoussu@goussu: ~/library/go/exercises
~/library/go/exercises cat protohackers-ladder-subject.md

Protohackers ladder — subject

# Per-rung problem statements for Protohackers 0–5 in Go — wire-format essentials, local test recipes, and the acceptance bar of a clean race detector plus a green online checker.

Subjectsaved 2026-08-08source #exercise#go#networking#concurrency#goroutines#protohackers#subject

Brief

Six network servers, each graded by Protohackers' live checker hammering a public endpoint you provide, each one rung harder than the last: echo, a JSON protocol, a binary protocol, shared-state chat, a UDP store, and a man-in-the-middle proxy. You write them in Go, one concurrency pattern per rung. The statements below are enough to build against offline; the checker is the final examiner.

Instructions

Rung 0 — Smoke Test (TCP echo)

Echo per RFC 862 semantics: accept TCP connections; for each, send back exactly the bytes received, unmodified, until the client closes its write side (EOF); then flush and close. Handle at least 5 concurrent clients. Pattern: accept loop + goroutine per connection; this skeleton is reused by every later rung.

Rung 1 — Prime Time (line-delimited JSON)

Newline-delimited JSON protocol, one request per line: {"method":"isPrime","number":<JSON number>} → respond {"method":"isPrime","prime":<bool>}\n. Rules: method must be exactly "isPrime" and number a JSON number, else the request is malformed — reply with any malformed response and disconnect that client. Non-integers (and negatives) are never prime; big values must not crash you. Multiple requests per connection, in order; concurrent clients independent. Pattern: bufio.Scanner per connection, validate every field.

Rung 2 — Means to an End (binary, per-connection state)

Fixed 9-byte messages: 1 type byte + two big-endian int32s. I <timestamp> <price> inserts a price observation. Q <mintime> <maxtime> asks for the arithmetic mean of prices with mintime ≤ timestamp ≤ maxtime; reply with one big-endian int32 (0 or anything for an empty/invalid range). Each connection's data is private to it — no sharing between clients. Pattern: io.ReadFull for exact frames, encoding/binary, per-connection state only.

Rung 3 — Budget Chat (shared state)

Line-based ASCII chat over TCP (\n-terminated messages):

  • On connect, send a name prompt. The first line is the user's name: alphanumeric only, at least 1 character; reject illegal names (message + disconnect).
  • On join: announce * <name> has entered the room to the others; send the joiner a single line listing present users; relay every subsequent line as [<name>] <msg> to all other users (never back to the sender); on disconnect announce * <name> has left the room. Names are unique while connected.
  • Solve it twice: once with a mutex-guarded room map, once with a monitor goroutine owning the room and serving joins/leaves/broadcasts over channels. Keep both; form an opinion in a short note.

Rung 4 — Unusual Database Program (UDP)

A key-value store over UDP datagrams (requests under ~1000 bytes):

  • A datagram containing = is an insert: split at the first =; left is key, right (including any further =) is value; later inserts overwrite. Inserts get no response.
  • A datagram without = is a retrieve: respond with a single datagram key=value (current value, or your choice for a missing key).
  • The key version is reserved: retrieving it returns your server's version string; inserts to it are ignored.
  • No connections, no ordering, possible loss — note where retries/idempotency now sit with the client. Pattern: single net.PacketConn loop; state guarded or owned.

Rung 5 — Mob in the Middle (MITM proxy)

A malicious proxy for Budget Chat: listen locally; for each client, dial the upstream chat server and relay traffic in both directions concurrently, line by line — rewriting any Boguscoin address in chat messages to Tony's address (7YWHMfk9JZe0LM0g1ZauHuiSxhI). A Boguscoin address: starts with 7, 26–35 alphanumeric characters, delimited by spaces or the message boundaries. Partial reads must never split your rewriting — frame on \n before touching the payload. Propagate either side's disconnect to the other. Pattern: two copy goroutines per session plus teardown discipline.

Constraints

  • Go, standard library only.
  • Race-detector-clean rule: every rung's tests pass under go test -race, and the server runs clean under -race while being checked. A data race fails the rung regardless of the checker's verdict.
  • A rung is complete only when the online checker passes it — local green is necessary, not sufficient — and no rung starts before the previous one passes.

Acceptance

Mapped one-to-one onto the exercise's milestones — for each rung: the Protohackers online checker passes against your deployed endpoint, go test -race is green, plus the local recipe:

  1. Smoke Testprintf 'hello' | nc host 8000 echoes back; 5 parallel nc sessions echo independently; EOF closes cleanly.
  2. Prime Time — table-driven tests over primes, composites, non-integers, huge numbers, and malformed JSON; a malformed line disconnects only that client.
  3. Means to an End — a scripted binary session (inserts then range queries, including empty range) gets correct means; two concurrent clients never see each other's data.
  4. Budget Chat — a three-terminal nc session shows prompt, join/leave announcements, the presence list, and relay-to-others-only; both implementations (mutex and monitor goroutine) pass the same tests; the comparison note exists.
  5. Unusual Database Programnc -u session verifies insert silence, retrieve, overwrite, first-= splitting, and the immutable version key.
  6. Mob in the Middle — through the proxy, a chat session works end to end and a message containing a Boguscoin address arrives rewritten; addresses at start, middle, and end of message all rewrite; non-matching 7-strings pass untouched.

Related