Versioned Document Store
Reported by candidates from Notion's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.
The Notion OA reported in October 2023 hands you a versioned document store, and the detail that matters is in the constraints: CREATE timestamps are strictly increasing per document name. That one line decides your whole design. It's a hash map of name to a list of versions, plus a binary search for GET_AT. Nothing exotic, but 200000 operations and timestamps up to 10^18 punish sloppy code. If you blank mid-assessment, StealthCoder runs invisibly on your desktop and gives you a working solution in real time. Here's the script before you need it.
The problem
Maintain versions of named text documents. Process these operations in order: ["CREATE", timestamp, name, text] creates the first version of name or overwrites it with a new version at that timestamp. ["GET", name] returns the newest text for that document, or NULL when it has never been created. ["GET_AT", name, timestamp] returns the text from the newest version whose creation timestamp is at most the query timestamp, or NULL when no such version exists. Return the values produced by GET and GET_AT operations in order. Function runVersionedDocumentStore(operations: String[][]) → String[] Examples Example 1 operations = [["CREATE","0","doc","ABC"],["CREATE","3","doc","BCD"],["CREATE","6","doc","CDE"],["GET_AT","doc","1"],["GET_AT","doc","4"],["GET_AT","doc","7"],["GET","doc"]] return = ["ABC","BCD","CDE","CDE"] Each historical lookup selects the latest creation at or before its timestamp. GET returns the newest version. Constraints 1 <= operations.length <= 200000. Timestamps are decimal integers in [0, 10^18]. CREATE timestamps are strictly increasing for each document name. Names are non-empty ASCII strings containing no whitespace. Text is a printable ASCII string containing neither | nor a newline. The total length of returned text is at most 1000000.
Reported by candidates. Source: FastPrep
Pattern and pitfall
Store a hash map from document name to two parallel lists: timestamps and texts. CREATE appends to both, which keeps each list sorted for free because timestamps strictly increase per name. GET returns the last text, or NULL if the name is missing. GET_AT binary searches for the rightmost timestamp that's at most the query, which is an upper-bound search minus one. The pitfalls are easy to hit. Timestamps reach 10^18, so parse them as 64-bit integers, and watch for overflow in languages with 32-bit defaults. Don't compare timestamps as strings. Return the literal NULL for misses, and remember the output only collects GET and GET_AT results. Don't scan versions linearly, since that's quadratic on the worst case. If the binary search logic slips under pressure, StealthCoder is the hedge that keeps you moving during the live OA.
Memorize the pattern. If you can't, run StealthCoder. The proctor sees the IDE. They don't see what's behind it.
You can drill Versioned Document Store cold, or you can hedge it. StealthCoder runs invisibly during screen share and surfaces a working solution in under 2 seconds. The proctor sees the IDE. They don't see what's behind it. Made by an engineer who treats the OA as theater. If yours is tonight, you don't have time to grind. You have time to hedge.
Get StealthCoderRelated leaked OAs
This OA pattern shows up on LeetCode as time based key value store. If you have time before the OA, drill that.
You've seen the question.
Make sure you actually pass Notion's OA.
Notion reuses patterns across OAs. Made by an engineer who treats the OA as theater. If yours is tonight, you don't have time to grind. You have time to hedge. Works on HackerRank, CodeSignal, CoderPad, and Karat.
Versioned Document Store FAQ
What's the trick in the Notion versioned document store problem?+
Per-name sorted history. Since CREATE timestamps strictly increase for each document, you just append versions to a list and binary search it for GET_AT. A hash map keyed by name gives you the list. GET is simply the last element.
How hard is this OA question really?+
Easy to medium. There's no tricky algorithm, just a hash map and a binary search. The difficulty is clean implementation: 64-bit timestamp parsing, the NULL cases, and an off-by-one in the search. Most people who know bisect-style searches finish it quickly.
What should GET_AT return when the timestamp is before the first version?+
NULL. If the query timestamp is smaller than the earliest creation timestamp, no version qualifies. Your binary search will land on index -1 after the upper-bound step, so check for that before indexing. The same goes for a name that was never created.
Can I just loop through versions for each query?+
Not safely. With up to 200000 operations, a document with many versions makes repeated linear scans quadratic. Binary search gives O(log k) per GET_AT, and appending is O(1). Use that and you won't have to worry about the limits.
How do I prepare for this in 48 hours?+
Practice the upper-bound binary search pattern until you can write it without thinking, then write a small hash map of lists solution end to end. Test the edge cases: missing name, query before first version, query exactly on a timestamp, and query after the last.