In-Memory Key-Value Database with Nested Transactions
Company: Anthropic
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates a candidate's ability to design a data structure that layers transactional state over a committed base, testing skills in nested scope management and state isolation. It probes practical application of stack-based rollback logic and efficient in-memory data modeling, a common way coding interviews assess system design at the data-structure level.
Constraints
- 1 <= len(commands) <= 10^5
- Keys and values are non-empty strings of length up to 100 with no spaces.
- GET on an unset key returns the literal string "NULL".
- COMMIT / ROLLBACK with no open transaction returns "NO TRANSACTION".
- COUNT should be efficient and not rescan all keys on every call.
Examples
Input: [["SET","a","10"],["GET","a"],["BEGIN"],["SET","a","20"],["BEGIN"],["DELETE","a"],["GET","a"],["ROLLBACK"],["GET","a"],["COMMIT"],["GET","a"]]
Expected Output: ["10","NULL","20","20"]
Explanation: GET a=10; after the nested DELETE it is NULL; the inner ROLLBACK restores a=20; COMMIT flattens a=20 into base; final GET a=20.
Input: [["COMMIT"]]
Expected Output: ["NO TRANSACTION"]
Explanation: COMMIT with no open transaction reports NO TRANSACTION.
Input: [["ROLLBACK"]]
Expected Output: ["NO TRANSACTION"]
Explanation: ROLLBACK with no open transaction reports NO TRANSACTION.
Input: [["SET","a","x"],["SET","b","x"],["SET","c","y"],["COUNT","x"],["COUNT","y"],["COUNT","z"],["DELETE","a"],["COUNT","x"]]
Expected Output: ["2","1","0","1"]
Explanation: Two keys map to x and one to y; z has none; after deleting a only b still maps to x.
Input: [["BEGIN"],["SET","k","1"],["BEGIN"],["SET","k","2"],["COMMIT"],["GET","k"],["ROLLBACK"]]
Expected Output: ["2","NO TRANSACTION"]
Explanation: COMMIT flattens both nested transactions so k=2 is permanent; the later ROLLBACK finds no open transaction.
Input: [["SET","a","x"],["BEGIN"],["SET","b","x"],["COUNT","x"],["ROLLBACK"],["COUNT","x"],["GET","b"]]
Expected Output: ["2","1","NULL"]
Explanation: Inside the transaction two keys map to x; ROLLBACK undoes the SET b so COUNT x drops back to 1 and b is unset.
Input: []
Expected Output: []
Explanation: No commands produce no output.
Input: [["GET","missing"],["DELETE","missing"],["GET","missing"]]
Expected Output: ["NULL","NULL"]
Explanation: Reading an unset key returns NULL; deleting a missing key is a no-op.
Input: [["SET","a","1"],["BEGIN"],["SET","a","2"],["SET","a","3"],["DELETE","a"],["ROLLBACK"],["GET","a"]]
Expected Output: ["1"]
Explanation: Multiple writes within one transaction are all undone in reverse by a single ROLLBACK, restoring a=1.
Input: [["SET","a","1"],["BEGIN"],["SET","a","2"],["BEGIN"],["SET","a","3"],["COMMIT"],["GET","a"]]
Expected Output: ["3"]
Explanation: COMMIT flattens every open transaction at once, so the innermost value a=3 becomes permanent.
Hints
- Keep one flat dictionary that represents the currently-visible state (committed base plus all open transactions applied), so GET is a single lookup.
- Maintain a value -> count map that you update on every SET/DELETE, so COUNT is O(1) instead of scanning every key.
- For each open transaction keep an undo log: before each write, record the key's previous value (or that it was absent). ROLLBACK replays that log in reverse; COMMIT just discards all logs since the visible state already holds the changes.
- Because only the innermost transaction's writes are recorded in its own log, ROLLBACK naturally restores the enclosing transaction (or base) state before that BEGIN.