Skip to main content

Code Katas

Short, deliberate-practice drills. Each kata is 15-25 minutes. Repeat until fluent. Pick any one of your working languages; switch languages between repetitions to notice where patterns collapse.

How to Use

  1. Set a timer.
  2. Write only what the kata asks. Resist adding "extra" patterns.
  3. After the timer, spend five minutes writing the functional or plain-composition version of the same solution and compare.
  4. Log one lesson per repetition.

Kata 1: Builder for HTTP Request

Skill targeted: Builder, immutability, end-of-process invariants. Time limit: 20 minutes.

Build an HttpRequest value type with a fluent Builder:

  • required: url, method
  • optional: headers (map), body (bytes), timeout (Duration, default 30s)
  • invariants checked in build():
    • url is non-null and starts with http:// or https://
    • if method == "GET", body must be null
    • timeout > 0

Acceptance:

  • the built request is immutable
  • the call site reads like a recipe
  • exactly one test per invariant, failing with a clear message

Kata 2: Abstract Factory for Cross-Platform UI

Skill targeted: Abstract Factory, matching families. Time limit: 20 minutes.

Implement a UIFactory interface with createButton() and createLabel(). Provide two families: DesktopFactory (DesktopButton, DesktopLabel) and MobileFactory (MobileButton, MobileLabel).

Write a buildLoginScreen(factory: UIFactory) that builds a small screen by calling only factory methods.

Acceptance:

  • no if (platform) branches inside buildLoginScreen
  • the family is chosen once at the composition root
  • one test per family produces the expected component types

Kata 3: Adapter for a Third-Party API

Skill targeted: Adapter, boundary, exception mapping. Time limit: 25 minutes.

Given a fake third-party client:

class LegacyPayments {
fn doTx(amt_cents_str: string, card_token: string): {status: "OK"|"FAIL", tx_id?: string, err?: string}
}

Define a domain target interface:

interface PaymentGateway {
charge(amountCents: number, token: string): Promise<Receipt>
}

Write LegacyPaymentsAdapter that:

  • converts amountCents to the string the legacy API expects
  • returns a typed Receipt on success
  • throws a domain PaymentFailed exception on status == "FAIL" including the err field

Acceptance:

  • no legacy types leak outside the adapter
  • at least one unit test for both happy and failure paths

Kata 4: Decorator for Text Processing Pipeline

Skill targeted: Decorator stacking, ordering sensitivity. Time limit: 20 minutes.

Implement a TextProcessor interface with process(s: string): string and these decorators, each in its own small class:

  • Trim
  • NormalizeUnicode (NFC)
  • CollapseWhitespace
  • Lowercase
  • RemovePunctuation

Compose two different pipelines at the call site. Write one test that proves swapping Lowercase and RemovePunctuation changes the output for a specific input, pinning the ordering contract.

Acceptance:

  • one decorator per class, each under ten lines
  • pipelines composed outside the decorator classes
  • at least one ordering-sensitive test

Kata 5: Composite for a File Tree

Skill targeted: Composite, uniform treatment, recursion. Time limit: 20 minutes.

Model a file system with a FsNode interface, File (leaf) and Directory (composite). Implement:

  • sizeBytes() recursively
  • find(predicate) that returns a flat list of matching nodes at any depth
  • print(indent) that renders the tree

Acceptance:

  • no external code branches on "is it a file or a directory"
  • Directory.sizeBytes() is a one-line reduce/sum over children
  • at least one test tree mixing leaves and sub-directories three levels deep

Kata 6: Proxy for Lazy Image Loading

Skill targeted: Proxy (virtual + protection), same interface, controlled access. Time limit: 25 minutes.

Given an Image interface with display():

  1. Implement HighResImage(path) whose constructor "loads" the image (simulate with a 50 ms sleep and a load counter).
  2. Implement LazyImage(path) that creates the HighResImage only on the first display().
  3. Implement RestrictedImage(inner, user) that throws if user.mayViewImages is false.
  4. Compose RestrictedImage(LazyImage(...)) and assert that unauthorized access does not trigger a load at all.

Acceptance:

  • HighResImage is constructed zero times until a permitted display() is called
  • one test proves lazy load; one proves permission check; one proves the two combine without double-wrapping
  • write one sentence explaining why one of these is a Proxy and not a Decorator

Bonus Kata 7: Delete a Pattern

Skill targeted: judgment. Time limit: 30 minutes.

Find a pattern in an existing codebase that you suspect is cargo-culted (see the misuse concept page). Do three things:

  1. Remove it and inline the work.
  2. Run the tests.
  3. Write a short PR description that argues the change improved the code.

If the deletion fails tests in a way that proves the pattern was earning its keep, reintroduce it and write the opposite memo: why the pattern is non-negotiable here.


Bonus Kata 8: Collapse a Pattern to a Function

Skill targeted: simpler alternatives. Time limit: 20 minutes.

Take one class-based Strategy or Decorator from your language of choice and rewrite it as a closure or higher-order function. Commit a side-by-side comparison. Pick one; justify.

Integrated OOAD Pattern Katas

Restaurant Management Model

Build a small object model for reservations, tables, servers, orders, menu items, and payments.

Tasks:

  1. Use composition for objects that have strict lifecycle ownership.
  2. Use dependency injection for payment or notification gateways.
  3. Add one adapter around a fake external service.
  4. Decide whether a factory is useful for order creation or whether a constructor is enough.
  5. Write a "when not to use this pattern" note for every pattern you introduced.

Evidence check: tests must prove object collaboration, not just object construction. The design note must name coupling introduced by each abstraction.