Requirements checklist
- Enumerate the algorithms before choosing — fixed window (boundary burst!), sliding log (memory), sliding window counter, token bucket — with each one's burst behavior stated.
- Interface:
allow(key) -> boolean; library, in-process; per-key config overrides. - Ask the memory question: millions of keys — state must be bounded.
- Failure stance (open vs closed) is the caller's choice, not the library's.
The model
allow(key): state = store.get(key) // resolved config cached on state refill lazily from elapsed time // no timers, integer math tokens >= 1 ? consume, allow : deny (+ derived retry hint) idle eviction is LOSSLESS: an idle bucket refills to full, and a missing bucket is re-created full — same thing.
Principles, at their decisions
- Single responsibility: algorithm behind one interface; the keyed store knows nothing about tokens.
- Open/closed: new algorithms slot behind the seam;
allow's contract never moves. - Dependency inversion: time injected as a clock parameter — every claim testable.
Complexity facts
allow: O(1) — a map lookup, integer refill arithmetic, a compare.- Memory: O(active keys), bounded by lossless idle eviction.
- No timers, ever: refill computed at read from elapsed time.
The five report dimensions, for this problem
- Requirements & interface — enumerated algorithms with burst behavior; asked about key cardinality and failure stance.
- Core design & invariants — lazy refill, bounded store, lossless eviction stated as an invariant.
- Extension probe — locate the new requirement at a seam; narrate what changes and what the hot path keeps.
- Complexity honesty — O(1) with its because; staleness/accuracy trade-offs quantified.
- Communication — decisions defended as stances, not defaults.
Ready? Sit the live mock → — the interviewer will run a twist this chapter deliberately hasn't shown you.