Design a transactional in-memory key–value store
Company: Applied Intuition
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of transactional state management, nested transaction semantics, scope shadowing, and efficient data-structure design for an in-memory key–value store, assessing competency in correctness, consistency, and algorithmic complexity under nested BEGIN/ROLLBACK/COMMIT operations.
Constraints
- 0 <= len(commands) <= 200000
- Each command is valid and uses one of the five supported operations
- Keys and values contain no spaces
- Average-case hash map operations may be assumed to be O(1)
Examples
Input: (["SET a 10", "GET a", "GET b"],)
Expected Output: ["10", "NULL"]
Explanation: Key a is stored with value 10. Key b was never set, so GET b returns NULL.
Input: (["SET a 10", "BEGIN", "SET a 20", "BEGIN", "SET b 30", "GET a", "GET b", "ROLLBACK", "GET b", "GET a", "COMMIT", "ROLLBACK", "GET a"],)
Expected Output: ["20", "30", "NULL", "20", "NO TRANSACTION", "20"]
Explanation: The inner transaction adds b=30 and is rolled back, so b disappears. The outer transaction keeps a=20. COMMIT makes that permanent, so a later ROLLBACK fails with NO TRANSACTION.
Input: (["SET x 1", "BEGIN", "SET x 2", "SET x 3", "BEGIN", "SET x 4", "GET x", "ROLLBACK", "GET x", "ROLLBACK", "GET x"],)
Expected Output: ["4", "3", "1"]
Explanation: Within the outer transaction, x changes from 1 to 2 to 3. The inner transaction temporarily changes x to 4, then rolls back to 3. Rolling back the outer transaction restores x to its original value 1.
Input: (["GET z", "ROLLBACK", "COMMIT"],)
Expected Output: ["NULL", "NO TRANSACTION", "NO TRANSACTION"]
Explanation: GET on a missing key returns NULL. With no active transaction, both ROLLBACK and COMMIT return NO TRANSACTION.
Input: ([],)
Expected Output: []
Explanation: An empty command list produces no output.
Solution
def solution(commands):
store = {}
tx_stack = []
outputs = []
MISSING = object()
for command in commands:
parts = command.split()
op = parts[0]
if op == "SET":
key, value = parts[1], parts[2]
if tx_stack and key not in tx_stack[-1]:
tx_stack[-1][key] = store.get(key, MISSING)
store[key] = value
elif op == "GET":
key = parts[1]
outputs.append(store.get(key, "NULL"))
elif op == "BEGIN":
tx_stack.append({})
elif op == "ROLLBACK":
if not tx_stack:
outputs.append("NO TRANSACTION")
else:
changes = tx_stack.pop()
for key, old_value in changes.items():
if old_value is MISSING:
store.pop(key, None)
else:
store[key] = old_value
elif op == "COMMIT":
if not tx_stack:
outputs.append("NO TRANSACTION")
else:
tx_stack.clear()
return outputsTime complexity: O(n) total for n commands on average; SET, GET, BEGIN, and COMMIT are O(1) average, while ROLLBACK is O(k) where k is the number of distinct keys changed in the most recent transaction.. Space complexity: O(m + c), where m is the number of keys currently stored and c is the total number of first-change log entries across all open transactions..
Hints
- Do not copy the entire database on every BEGIN. Store only what changes in each transaction.
- When a key is set multiple times in the same transaction, log its previous value only the first time it changes in that transaction.