Design a Multi-Level Expiring Item Store
OptiverSoftware Engineer · Online AssessmentLast updated
Design an in-memory multi-level key-value store where items are stored at specific priority levels with individual expiration timestamps, supporting query fallback and expiration cleanup.
Problem Statement
In low-latency trading infrastructure, caches are frequently partitioned into multiple hierarchical tiers (levels), where each tier has distinct latency, priority, or persistence characteristics. Items stored in these tiers expire after a set time.
Implement the MultiLevelStore class that manages items across num_levels ordered tiers (indexed from 0 to num_levels - 1, where level 0 has the highest priority).
Class Interface
class MultiLevelStore:
def __init__(self, num_levels: int):
"""
Initializes the store with the given number of storage levels.
Levels are indexed 0 to num_levels - 1.
"""
pass
def store(self, key: str, value: int, level: int, expires_at: int) -> None:
"""
Stores or updates an item at the specified level with an absolute
expiration timestamp (expires_at). If the key already exists at this
level, overwrite its value and expires_at.
"""
pass
def retrieve(self, key: str, current_time: int) -> int | None:
"""
Searches for key from level 0 up to level num_levels - 1.
Returns the value from the lowest-indexed level where key is present
and not expired (expires_at > current_time).
Expired entries encountered during the lookup should be removed.
Returns None if key is not found or is expired at all levels.
"""
pass
def purge_expired(self, current_time: int) -> int:
"""
Removes all expired items (expires_at <= current_time) across all levels.
Returns the total number of items removed.
"""
pass
Constraints
1 <= num_levels <= 160 <= level < num_levels1 <= len(key) <= 64, consisting of alphanumeric characters0 <= current_time, expires_at <= 2 * 10^9- Values are standard signed 32-bit integers.
- Total number of method calls does not exceed
10^5.
Example
store = MultiLevelStore(3)
# Store key "bid" at level 1 and level 0 with different expirations
store.store("bid", 100, 1, 50) # Level 1: "bid" expires at 50
store.store("bid", 105, 0, 30) # Level 0: "bid" expires at 30
store.store("ask", 110, 2, 40) # Level 2: "ask" expires at 40
# At time 20, both level 0 and level 1 have "bid". Level 0 takes priority.
assert store.retrieve("bid", 20) == 105
# At time 35, level 0 "bid" has expired (30 <= 35), falls back to level 1
assert store.retrieve("bid", 35) == 100
# At time 45, level 2 "ask" has expired (40 <= 45)
assert store.retrieve("ask", 45) is None
# Purge expired items at time 60
# Level 1 "bid" is now expired (50 <= 60). Level 0 "bid" and level 2 "ask"
# were already lazily evicted by retrieve(). Only 1 item remains to be purged.
assert store.purge_expired(60) == 1
Edge Cases to Consider
- Overwriting a key at a given level before it expires with a new value and earlier/later expiration timestamp.
- Calling
retrievewhen an item is expired at level 0 but valid at level 1; ensure the expired item at level 0 is properly cleaned up and does not mask the valid item at level 1. - Invocations where
expires_at == current_timeshould be treated as expired.
Hints
Hint 1
Use a hash map per level to maintain O(1) lookups and updates for keys.
Hint 2
For efficient purge operations without iterating over all keys, consider tracking expiration times per level using a min-heap or ordered list alongside the hash map.
Hint 3
When updating a key at a level, handle 'stale' entries in your priority queue either by updating the heap or by storing a generation/timestamp token to ignore outdated heap entries lazily.
Follow-up questions
- How would you extend this design if each level had a fixed capacity constraint and required an eviction policy (e.g., LRU + TTL)?
- How would you ensure thread-safety with fine-grained locking to maximize read throughput across levels?
- If timestamps strictly advance monotonically, how could you optimize `purge_expired` to avoid heap rebalancing overhead?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.