Time Based Key-Value Store — Solution in Python, Java & C++
Time Based Key-Value Store solution explained: step-by-step approach, complexity and code in Python, Java, C++ and JavaScript. A medium problem.
Time Based Key-Value Store is a medium coding problem on strings, hash table and binary search, asked in Amazon, Google, Meta and Uber interviews.
- Difficulty: Medium
- Topics: Strings, Hash Table, Binary Search
- Asked at: Amazon, Google, Meta, Uber
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Build a key-value store that remembers every value a key has ever held, each stamped with the time it was written, and can answer "what was this key's value at time t?".
Write the class TimeMap:
TimeMap()creates an empty store.void set(String key, String value, int timestamp)records thatkeyholdsvaluefrom timetimestampon.String get(String key, int timestamp)returns the value of thesetcall forkeywith the largest timestamp that is<= timestamp. Ifkeywas never set at or before that time, it returns the empty string"".
The judge calls the methods in the order listed in the first input line, with the arguments in the second; the expected output holds each call's result, null for the constructor and for every method that returns nothing.
Example 1
Input: ["TimeMap","set","get","get","set","get","get"]
[[],["fruit","apple",1],["fruit",1],["fruit",3],["fruit","mango",4],["fruit",4],["fruit",5]]
Output: [null,null,"apple","apple",null,"mango","mango"]
Explanation: At times 1 and 3 the latest value of fruit is apple. From time 4 on, mango replaces it.
Example 2
Input: ["TimeMap","set","set","get","get","get"]
[[],["k1","low",10],["k2","high",20],["k1",9],["k2",25],["k3",30]]
Output: [null,null,null,"","high",""]
Explanation: k1 was first set at time 10, so asking at time 9 gives the empty string. k2 at time 25 is high; k3 was never set.
Constraints
1 <= key.length, value.length <= 100key and value consist of lowercase English letters and digits1 <= timestamp <= 10^7The timestamps of all set calls are strictly increasingAt most 2 * 10^5 calls are made to set and get
Hints for Time Based Key-Value Store
Hint 1
Group the history by key: each key needs its own list of (timestamp, value) pairs.
Hint 2
Because the
settimestamps only ever increase, appending to a key's list keeps it sorted by time — no sorting needed.Hint 3
getwants the last entry with time<= timestamp: binary search the key's list for the first time greater thantimestampand step back one. If that falls before the start, answer"".
How to solve Time Based Key-Value Store
A hash map from key to that key's history. The histories are appended in time order, so each one is already sorted and a binary search finds the value in force at any time.
Approach
set(key, value, timestamp): append(timestamp, value)tohistory[key].get(key, timestamp): ifkeyhas no history, return"".- Binary search the history for the first entry whose time is greater than
timestamp; the entry just before it is the latest one at or beforetimestamp. - Return that entry's value, or
""if there is none (every write came later).
Why it works
The problem guarantees strictly increasing set timestamps, so every history list is sorted by time by construction. In a sorted list the entries <= timestamp form a prefix; its last element is the most recent write that had happened by then, which is exactly the value the key held.
Complexity
- Time —
O(1) per set, O(log m) per get for a key with m writes - Space —
O(total number of set calls)
Pitfalls
- Searching for the first time
>= timestampand stepping back one skips a write made exactly attimestamp, which must be returned. - A key that exists but whose first write is after
timestampmust give"", not its first value. - Scanning the whole list on every
getis O(m) and too slow at 2 × 10^5 calls.
Reference solution
Python
import bisect
class TimeMap:
def __init__(self):
self.times = {}
self.values = {}
def set(self, key: str, value: str, timestamp: int) -> None:
# set timestamps only increase, so each list stays sorted
self.times.setdefault(key, []).append(timestamp)
self.values.setdefault(key, []).append(value)
def get(self, key: str, timestamp: int) -> str:
ts = self.times.get(key)
if not ts:
return ""
i = bisect.bisect_right(ts, timestamp) - 1
return self.values[key][i] if i >= 0 else ""Java
class TimeMap {
private HashMap<String, TreeMap<Integer, String>> h = new HashMap<>();
public TimeMap() {
}
public void set(String key, String value, int timestamp) {
h.computeIfAbsent(key, k -> new TreeMap<>()).put(timestamp, value);
}
public String get(String key, int timestamp) {
TreeMap<Integer, String> t = h.get(key);
if (t == null) return "";
Map.Entry<Integer, String> e = t.floorEntry(timestamp); // latest write at or before timestamp
return e == null ? "" : e.getValue();
}
}C++
class TimeMap {
unordered_map<string, vector<pair<int, string>>> h;
public:
TimeMap() {}
void set(string key, string value, int timestamp) {
h[key].push_back({timestamp, value}); // set timestamps only increase
}
string get(string key, int timestamp) {
auto it = h.find(key);
if (it == h.end()) return "";
auto& v = it->second;
auto p = upper_bound(v.begin(), v.end(), timestamp,
[](int t, const pair<int, string>& e) { return t < e.first; });
return p == v.begin() ? "" : prev(p)->second;
}
};JavaScript
var TimeMap = function() {
this.h = new Map(); // key -> { ts: [], vs: [] }, sorted by time
};
TimeMap.prototype.set = function(key, value, timestamp) {
var e = this.h.get(key);
if (!e) { e = { ts: [], vs: [] }; this.h.set(key, e); }
e.ts.push(timestamp);
e.vs.push(value);
};
TimeMap.prototype.get = function(key, timestamp) {
var e = this.h.get(key);
if (!e) return "";
var lo = 0, hi = e.ts.length; // first time > timestamp
while (lo < hi) {
var mid = (lo + hi) >> 1;
if (e.ts[mid] <= timestamp) lo = mid + 1; else hi = mid;
}
return lo > 0 ? e.vs[lo - 1] : "";
};Also on the editorial tab: C, C#, Go, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 515 strings problems · the whole catalogue
Learn the technique: Strings · Hashing: Hash Maps and Hash Sets