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
- Set a timer.
- Write only what the kata asks. Resist adding "extra" patterns.
- After the timer, spend five minutes writing the functional or plain-composition version of the same solution and compare.
- 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():urlis non-null and starts withhttp://orhttps://- if
method == "GET",bodymust 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 insidebuildLoginScreen - 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
amountCentsto the string the legacy API expects - returns a typed
Receipton success - throws a domain
PaymentFailedexception onstatus == "FAIL"including theerrfield
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:
TrimNormalizeUnicode(NFC)CollapseWhitespaceLowercaseRemovePunctuation
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()recursivelyfind(predicate)that returns a flat list of matching nodes at any depthprint(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():
- Implement
HighResImage(path)whose constructor "loads" the image (simulate with a 50 ms sleep and a load counter). - Implement
LazyImage(path)that creates theHighResImageonly on the firstdisplay(). - Implement
RestrictedImage(inner, user)that throws ifuser.mayViewImagesis false. - Compose
RestrictedImage(LazyImage(...))and assert that unauthorized access does not trigger a load at all.
Acceptance:
HighResImageis constructed zero times until a permitteddisplay()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:
- Remove it and inline the work.
- Run the tests.
- 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:
- Use composition for objects that have strict lifecycle ownership.
- Use dependency injection for payment or notification gateways.
- Add one adapter around a fake external service.
- Decide whether a factory is useful for order creation or whether a constructor is enough.
- 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.