Skip to content
Reliable Data Engineering
Practice problem medium binary-searchas-of-joinversioning
Solve it in the browser (Python editor)

Point-in-Time Lookup (As-Of Join)

Difficulty: Medium · Topics: binary-search, as-of-join, versioning · Asked at: Google, Stripe, Two Sigma, Databricks

Problem

Implement VersionedStore with set(key, value, ts) and get(key, ts) returning the value with the largest timestamp ≤ ts, or None. Timestamps for a key can arrive out of order. Then implement as_of_join(events, store) that enriches each (key, ts) event with the value valid at that time.

Starter code

class VersionedStore:
    def __init__(self):
        pass

    def set(self, key, value, ts: int) -> None:
        pass

    def get(self, key, ts: int):
        pass

def as_of_join(events, store):
    pass

Hints

Hint 1

Keep per key two parallel sorted lists (timestamps, values); bisect.insort handles out-of-order inserts.

Hint 2

bisect_right(timestamps, ts) - 1 is the index of the last timestamp ≤ ts.

Solution

import bisect
from collections import defaultdict

class VersionedStore:
    def __init__(self):
        self.ts = defaultdict(list)
        self.vals = defaultdict(list)

    def set(self, key, value, ts: int) -> None:
        i = bisect.bisect_right(self.ts[key], ts)
        self.ts[key].insert(i, ts)
        self.vals[key].insert(i, value)

    def get(self, key, ts: int):
        i = bisect.bisect_right(self.ts.get(key, []), ts) - 1
        return self.vals[key][i] if i >= 0 else None

def as_of_join(events, store):
    return [(k, t, store.get(k, t)) for k, t in events]

Tests

Your solution should pass these:

s = VersionedStore()
s.set("EURUSD", 1.08, 100)
s.set("EURUSD", 1.10, 300)
s.set("EURUSD", 1.09, 200)        # out of order
assert s.get("EURUSD", 99) is None
assert s.get("EURUSD", 100) == 1.08
assert s.get("EURUSD", 250) == 1.09
assert s.get("EURUSD", 10_000) == 1.10
assert s.get("GBPUSD", 500) is None
assert as_of_join([("EURUSD", 150), ("EURUSD", 300)], s) == [("EURUSD", 150, 1.08), ("EURUSD", 300, 1.10)]

Explanation

get is O(log n); set is O(n) in the worst case because of list insertion (O(log n) if timestamps arrive in order, the common case). This is exactly the point-in-time join used for FX rates on transactions and for feature stores (feature value as of label time, without leakage). pandas merge_asof and SQL ASOF JOIN do the same at scale with sorted merges instead of per-row binary search.