Problem
Build a KeyValueStore with per-key time-to-live driven by a caller-supplied LOGICAL clock — an integer tick counter, not the wall clock. put(k, v) stores a value. get(k) returns the value, or null if the key is absent or expired. setTTL(k, ttlTicks) marks the key to expire once the logical clock reaches setTime + ttlTicks. tick(n) advances the logical clock by n. isExpired(k) reports whether a key with a TTL has passed its expiry. A key with no TTL never expires. Time only moves through tick(n) — no Thread.sleep, no System.currentTimeMillis().
Restate the problem in one sentence and list the edge cases — before writing any code.
Then keep talking — restate the problem + the edge cases in your own words.
“Let me make sure I understand the problem, then I'll walk through my approach.”
checking microphone…
Self-check
Hints / what this doesn't test
This is THE canonical Opendoor phone-screen shape: a stateful component on a
logical clock, so the test stays deterministic. Restate it before you type —
"So time only moves when tick is called, setTTL pins an expiry relative to
now, and get returns null the moment the clock reaches that expiry — a key with
no TTL just lives forever?"
The off-by-one to say out loud: the spec is now >= setTime + ttl, so the key
is dead at the boundary tick — using > instead leaves it alive one tick too long.
And a fresh put should clear any stale TTL, otherwise an overwritten key can
silently inherit an old expiry.
What this problem does not test (name it): real-clock expiry, lazy vs. active eviction of dead keys to reclaim memory, TTL on overwrite semantics beyond the basic reset, and concurrency are out of scope — but a senior names that storing the absolute expiry tick (not a relative countdown) is what keeps this correct once the logical clock advances.
Tests
- ○put_get
- ○alive_before_ttl
- ○expires_after_ttl
- ○is_expired_flag
- ○overwrite_resets_value
- ○no_ttl_never_expires
- + 3 hidden tests — revealed once the visible ones pass