Rafael Romão's Coding Labs

22 labs · Java + Go · tests included

Rafael Romão's Coding Labs

Twenty-two backend components, each with a test suite that checks the contract.

Each lab is a small production-style component — a blocking queue, a rate limiter, an outbox, a route planner — with a written contract, a diagram, a starter stub in Java and in Go, and tests. Implement the stub until the tests pass, then compare with the reference solution.

Java starter projectGo starter projectReference solutions

  • IntelliJ IDEA projects
  • Gradle · Kotlin DSL · JDK 21
  • Go 1.25+ with -race
  • Diagram on every lab

Starter projects

Clone the repository and open one of the two project directories in IntelliJ IDEA. Every lab is a stub with the public API in place and TODO bodies; its tests fail until you implement it.

Java

Gradle with Kotlin DSL on a JDK 21 toolchain (downloaded if missing). Stubs in src/main/java/labs, tests in src/test/java/labs, plus an All labs run configuration.

cd java
./gradlew test
./gradlew test --tests 'labs.ttlcache.*'

Go

A Go module with one package per lab, for GoLand or IntelliJ IDEA with the Go plugin. CGO and a C compiler are needed for the two SQLite labs.

cd go
go test -race -count=1 ./...
go test -race -count=1 ./ttlcache

Four steps per lab

Tests you write yourself mostly confirm what you already believed. A supplied suite fails on the boundary you skipped, the tie you broke differently, or the interleaving you never imagined, so the order matters more than the code.

  1. Read the lab page. Work through The task: the scenario, the diagram, the API you implement, the rules and the worked example. Then open the lab's tests from Files and commands to see how each rule is checked.
  2. Implement the starter. In IntelliJ, open the lab's stub in java/ or go/ and replace every TODO, keeping the given signatures. Run its tests from the gutter icon until they pass, using the page's Steps when you get stuck.
  3. Compare. Only after your tests pass, reveal the reference solution from the lab page (it lives in solutions/) and explain one difference in representation or synchronization.
  4. Recall and vary. A few days later, do the page's Checklist: rebuild the core from memory, implement its variation, and add a test for an edge case you chose.

The 22 labs

Each lab page has the task, a diagram, the API, the rules, a worked example, implementation steps and a checklist. New here? Start with L05 Bounded TTL Cache: its tests show the shape the others expect — an injected clock, an inclusive deadline and a concurrent schedule.

Concurrency building blocks

  • L01Bounded Blocking Queue

    Build the bounded hand-off buffer between producer and consumer threads: put waits while full, take waits while empty, and close lets consumers drain what was already accepted.

  • L02Bounded Worker Pool

    Run submitted tasks on a fixed number of worker threads behind a fixed-size waiting line; reject immediately when both are full, and drain accepted work on close.

  • L03Concurrent Request Aggregator

    Run a map of named calls concurrently (bounded), wait for all of them, and return one value-or-exception outcome per name; one failure never cancels the others.

  • L04Per-Key Request Coalescing

    When many callers ask for the same key at once, run the expensive load once and let them all share its result; forget the key as soon as the load finishes.

  • L15Bounded Publish-Subscribe Component

    An in-process broadcast broker: every subscriber has a bounded mailbox, publish never waits, and a full mailbox drops that message for that subscriber only.

Caches and admission control

  • L05Bounded TTL Cache

    An in-memory cache with two limits: at most capacity entries (evict least-recently used) and a fixed time-to-live per write, driven by an injected clock.

  • L06LFU Cache

    A fixed-size cache that evicts the key used the fewest times (ties: least recently used), with O(1) get and put.

  • L07Per-Client Rate Limiter

    Per-client token buckets: each request spends a token or is rejected at once; buckets refill at a fixed rate up to a burst cap, and idle clients are forgotten.

Time, scheduling and capacity

  • L08Delayed Task Scheduler

    A timer queue keyed by id: schedule, replace or cancel a callback with a due time; runDue(now) runs everything due, earliest first, outside the lock.

  • L14Concurrent Reservation Component

    A booking calendar for a resource with N parallel units: accept a time-interval booking only if overlapping bookings never exceed capacity; extend it with multi-unit bookings and safe rescheduling.

  • L16Time-Indexed Key-Value Store

    A versioned key-value store: put(key, time, value) records history, and get(key, t) returns the value as of time t — the latest entry at or before it.

  • L17Time-Window Event Aggregator

    A rolling window over timestamped values: exact count, max and top-K for the last width time units, with a forward-only watermark that rejects late events.

Reliability and data integrity

  • L09Retry and Circuit Breaker Executor

    Wrap calls to a flaky dependency with bounded, opt-in retries plus a circuit breaker that fails fast for a cooldown after repeated failures and then lets one probe through.

  • L10Durable Idempotent Operation

    A retry-safe add(scope, key, amount) on a SQLite counter: the first call increments and stores its response in the same transaction; repeats return the stored response.

  • L11Outbox and Idempotent Consumer

    Transactional outbox, both halves: save a command and its outgoing event in one transaction, relay unsent events in batches, and apply each event at most once on the consumer via an inbox table.

  • L18Streaming Record Processor

    Stream a file of one integer per line and return count and sum without loading it, failing the whole run on any malformed line, oversized line or overflow.

Graphs and hierarchies

  • L12Dependency Task Executor

    Run tasks that depend on other tasks: reject bad graphs (unknown dependency, cycle) before running anything, then execute ready tasks in bounded parallel waves and skip anything downstream of a failure.

  • L13In-Memory File System

    An in-memory file tree addressed by absolute paths: mkdir, write/read bytes, sorted list, and atomic move of a whole subtree.

  • L21Route Planner

    Cheapest path on a directed weighted graph with Dijkstra, then A* with an admissible heuristic; extend with forbidden turns by searching over roads instead of junctions.

Geospatial

  • L19Nearby-Object Search

    Index moving objects by latitude/longitude and answer “who is within R metres?” and “which k are nearest?”, using 1° latitude bands to skip impossible candidates and haversine distance to confirm.

  • L20Geofencing Engine

    Report enter/exit events for objects moving around one circular zone, with a hysteresis margin against boundary flapping and per-object sequence numbers that discard out-of-order updates.

  • L22Trajectory Processor

    A bounded, time-ordered GPS history per vehicle: segment distances and speeds, flags for impossible jumps, and snapping a point to the nearest road segment.

Supporting code

The starters ship two helpers fully implemented: algorithms (indexed heap, trie, union-find, top-K, Fenwick and segment trees, Bloom filter and more) and the haversine distance in the nearby-search lab, reused by the geofence and trajectory labs. solutions/ also holds examples that are not labs:

  • spatial — exact planar predicates, a quadtree and versioned fence replacement.
  • fencing, cells, httpio — fencing tokens, H3/S2 cell round trips, bounded HTTP bodies with deadlines.
  • postgres — a READ COMMITTED adapter and a one-batch relay; run with docker compose up and ./gradlew integrationTest.

Where the tests stop

A green suite is evidence about the cases someone wrote, not proof over every schedule. The components are local and in memory unless a lab says otherwise; clocks are injected, so real wall-clock drift is untested; mutable values are not deep-copied. When you reason about production readiness, name the limit you would test next.

A few Go tests in solutions/go inspect internal fields; the starter versions check the same behaviour through the public API so that your design stays your own.