Implement a Key-Value Cache with Fixed TTL, Renewal and a Live-Entry Count
Company: Oracle
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: easy
Interview Round: Onsite
Implement an in-memory key-value cache in which every entry expires a fixed time-to-live (TTL) after it was last written. The exercise follows the pattern of a common token-expiry design problem: the TTL is set once when the cache is created, the current time is passed into every operation, a live entry can be renewed, and the cache can report how many entries are still live. You are expected to write your own test cases.
A reasonable interface:
```python
class TTLCache:
def __init__(self, ttl: int) -> None: ...
def put(self, key: str, value: object, now: int) -> None: ...
def get(self, key: str, now: int) -> object | None: ...
def renew(self, key: str, now: int) -> bool: ...
def count_live(self, now: int) -> int: ...
```
- `put` inserts a new entry or overwrites an existing one; the entry expires at `now + ttl`.
- `get` returns the value of a live entry, or `None` if the key is missing or has expired.
- `renew` moves the expiry of a live entry to `now + ttl` and reports whether it did so. An expired entry is not revived.
- `count_live` returns how many entries are live at time `now`.
```hint Make the boundary testable
Your tests have to land exactly on the moment an entry expires, and on either side of it. Keep time an explicit input, and decide in writing what happens at that exact moment.
```
```hint Look for an order you get for free
`count_live` may be called far more often than entries change. With one TTL shared by every entry, think about which entries must expire first.
```
### Constraints and Clarifications
- The TTL is a positive integer, fixed for the lifetime of the cache.
- Time values are integers in the same unit as the TTL.
- The tests are part of the answer: you are expected to design them, not only the class.
### Clarifying Questions
- Is an entry that expires exactly at time `now` still live at `now`?
- Are the `now` values passed across calls guaranteed never to decrease?
- Does `get` extend an entry's lifetime, or only `put` and `renew`?
- Does `put` on a key whose entry has already expired behave like a fresh insert?
- Is there a capacity limit, and if so, what happens when the cache is full?
- Can a stored value be `None`, which would make a `None` result from `get` ambiguous?
### What a Strong Answer Covers
- A precise expiry boundary, applied identically in `get`, `renew` and `count_live`.
- Renewal that refuses expired entries, and clear overwrite semantics for `put`.
- An efficient way to discard expired entries and count live ones, with the complexity of every operation.
- A self-written test suite that covers the exact expiry moment, renewal before and at expiry, overwrites, counting after mixed writes, and invalid time input.
### Follow-up Questions
- How would the design change if each entry could have its own TTL?
- How would you add a maximum capacity with least-recently-used eviction on top of expiry?
- How would you make the cache safe for concurrent callers, and would lazy cleanup still be enough?
- If time came from a system clock instead of a parameter, how would you keep the tests deterministic?
Overview: Implement an in-memory key-value cache whose entries expire a fixed time-to-live after their last write, with put, get, renew and a count of live entries, and write your own tests for it. It tests expiry boundary semantics, renewal rules, efficient cleanup of expired entries and test design.
Read the full Oracle Software Engineer interview experience this question came from