Implement-Then-Extend Problems
- Run the implement-then-extend loop: build a clean working base, then absorb the live change
- Work three runnable problems: a transactional store, a checkpointed iterator, a multi-level limiter
- Pick the base data structure that makes the follow-up one more push instead of a rewrite
The xAI coding round has a shape, and the shape is the test. You are handed one component to implement and get it running, and then the interviewer changes the requirements while you watch. A feature is bolted on, a constraint is tightened, or a case you did not model becomes the case. Treat the opening prompt as the base you will extend. A candidate who reached a passing first version through small methods and cleanly separated state absorbs the twist in a few lines, while one who hard-coded an assumption starts over under a shrinking clock. This lesson works three of the recurring components in full, in the order the round unfolds: the simple core first, then the live extension it is built to take.
The implement-then-extend loop
Every problem in this round yields to the same discipline. First, scope the base with a clarifying question or two, then implement the smallest correct version out of small, independent operations, testing each against a case you name before the interviewer does. Second, when the extension lands, resist rewriting: ask what the new rule changes about the state you already hold, and reach for the smallest structural change that carries it. The usual failure is racing to a tangled first version that happens to pass, which has nowhere to grow when "now make it nestable" or "now add a second limit" arrives. Clean and extensible beats fast and fragile here, and the interviewer is grading the extensible part.
The base you can extend is almost always the base built from separated state. In the key-value store, that state is the transaction stack, one overlay per open transaction, so nesting is a push and rollback a pop. The iterator holds its read position as plain fields, so checkpointing is just copying those fields out. The rate limiter keeps one window per rule, so a second rule is one more window checked with an and. Name the piece of state the extension will touch and keep it in its own structure from the first line, and the follow-up becomes an edit.
Key-value store: growing nested transactions
The most-cited xAI problem is an in-memory key-value store, and it is the cleanest illustration of the whole loop. The base is trivial: set a key, get a key, delete a key, backed by one dictionary. Then the interviewer adds a transaction layer with begin, commit, and rollback, so a block of writes can be discarded as a group. Then comes the constraint that separates a passing answer from a strong one. Nested transactions mean a begin inside an open transaction has to commit into its parent or roll back on its own, independently of anything above it. The structure you pick for the transaction layer decides whether nesting costs you three lines or a teardown.
The draft that dies here keeps a single boolean, "am I in a transaction," and one side buffer of pending writes, which handles exactly one level. The structure that grows is a stack of overlay dictionaries: committed data sits in a base dictionary, and each open transaction pushes its own overlay on top. A read walks the stack from the newest overlay down to the base and returns the first value it finds. A write lands in the topmost overlay, or in the base when no transaction is open, and commit folds the top overlay into the one beneath it while rollback discards it. Nesting is free, because a begin inside a transaction is just one more push. The one subtlety is delete inside a transaction: since a rollback must bring the key back, it writes a tombstone marker into that layer, which shadows the value below until it resolves.
In-Memory Key-Value Store With Nested Transactions
Problem
KVStore supporting reads, writes, deletes, and nested transactions:set(key, value) stores a value. get(key) returns the current value for a key, or null if the key is not set. delete(key) removes a key.begin() opens a new transaction. commit() closes the innermost open transaction, folding its changes into the enclosing transaction if one is open, or into the committed store otherwise. rollback() closes the innermost open transaction and discards all of its changes.Transactions nest: a
begin inside an open transaction creates a child whose commit or rollback resolves independently of its parent. A delete performed inside a transaction must be undoable by a rollback.Input: set("a","1"); begin(); set("a","2"); get("a"); rollback(); get("a")
Output: "2", "1"
Inside the transaction the value is "2". Rolling back discards the transaction, so the committed "1" is restored.
Input: set("k","base"); begin(); set("k","outer"); begin(); set("k","inner"); commit(); get("k"); rollback(); get("k")
Output: "inner", "base"
The inner commit folds "inner" into the outer transaction, so get sees "inner". Rolling back the outer transaction discards everything, restoring "base".
Keep committed data in a base dictionary and one overlay dictionary per open transaction on a stack. Read newest-overlay-first down to the base. Write to the top overlay. Commit folds the top overlay down one level; rollback pops it. Model an in-transaction delete as a tombstone in the overlay so rollback can restore the shadowed value.
In-Memory Key-Value Store With Nested Transactions
Click "Run" to execute your code
Test Cases
[["KVStore",[]],["set",["a","1"]],["set",["b","2"]],["get",["a"]],["delete",["a"]],["get",["a"]],["get",["b"]]]Expected: [null,null,null,"1",null,null,"2"][["KVStore",[]],["set",["a","1"]],["get",["a"]],["begin",[]],["set",["a","2"]],["get",["a"]],["begin",[]],["set",["a","3"]],["delete",["a"]],["get",["a"]],["rollback",[]],["get",["a"]],["commit",[]],["get",["a"]],["get",["b"]]]Expected: [null,null,"1",null,null,"2",null,null,null,null,null,"2",null,"2",null][["KVStore",[]],["set",["k","base"]],["begin",[]],["set",["k","outer"]],["begin",[]],["set",["k","inner"]],["commit",[]],["get",["k"]],["rollback",[]],["get",["k"]]]Expected: [null,null,null,null,null,null,null,"inner",null,"base"][["KVStore",[]],["set",["x","10"]],["begin",[]],["delete",["x"]],["get",["x"]],["commit",[]],["get",["x"]]]Expected: [null,null,null,null,null,null,null]set takes an expiry timestamp and get takes the current time, so a key read after its expiry returns null as if unset. Store the expiry alongside the value in each overlay entry. Have the newest-overlay-first read skip any entry whose expiry has passed, treating it like the tombstone case. The transaction machinery does not change. Expiry rides along inside the same overlay values that commit and rollback already move, so the feature is a check at read time with no new layer of state.Stateful iterator: checkpoint and resume
The second recurring shape is an iterator, and checkpointing is the extension that makes it an xAI problem. The base is an ordinary iterator over run-length encoded pairs, where ["a", 2] means the value "a" repeated twice, exposed through next and has_next. The interviewer then adds get_state and set_state, so a caller can snapshot the iterator mid-stream, keep advancing, and later restore and resume from the exact snapshot point. The base draft that stalls hides its progress inside a Python generator or a decompressed list it cannot address. The version that extends holds its read position as plain fields, so a checkpoint is just copying those fields out and a restore is copying them back.
Stateful Iterator With Checkpoint and Resume
Problem
RLEIterator over a run-length encoded sequence. The constructor takes a list of [value, count] runs, so [["a",2],["b",1]] represents the stream a, a, b.next() returns the next value in the stream, or null when the stream is exhausted. has_next() returns whether any values remain.Extend it to be checkpointable:
get_state() returns a snapshot of the current position, and set_state(state) restores the iterator to a previously returned snapshot, so iteration resumes from exactly that point.Input: RLEIterator([["a",2],["b",1],["c",3]]); next(); next(); s = get_state(); next(); next(); set_state(s); next()
Output: "a", "a", "b", "c", "b"
After two calls the snapshot sits at the start of the "b" run. Two more calls advance to "b" then "c". Restoring the snapshot rewinds to "b", so the next call returns "b" again.
Input: RLEIterator([["x",1]]); has_next(); next(); has_next(); next()
Output: true, "x", false, null
One value in the stream. After it is consumed, has_next is false and a further next returns null.
Hold the read position as two plain fields: the index of the current run and how many of it you have consumed. Each next call reads the current run and advances those fields, rolling to the next run when the current one is used up. The get_state call returns a copy of the two fields, and set_state writes them back.
Stateful Iterator With Checkpoint and Resume
Click "Run" to execute your code
Test Cases
[["RLEIterator",[[["a",2],["b",1],["c",3]]]],["next",[]],["next",[]],["get_state",[]],["next",[]],["next",[]],["set_state",[[1,0]]],["next",[]],["next",[]],["has_next",[]]]Expected: [null,"a","a",[1,0],"b","c",null,"b","c",true][["RLEIterator",[[["x",1]]]],["has_next",[]],["next",[]],["has_next",[]],["next",[]]]Expected: [null,true,"x",false,null][["RLEIterator",[[["p",3]]]],["next",[]],["get_state",[]],["next",[]],["next",[]],["has_next",[]],["set_state",[[0,1]]],["next",[]],["has_next",[]]]Expected: [null,"p",[0,1],"p","p",false,null,"p",true]Multi-level rate limiter: one key, then compound rules
The third shape is a rate limiter, and its extension is a second rule. The base admits or denies a request against one limit, at most some number of requests per key within a sliding time window, decided from a stream of timestamps in nondecreasing order. The interviewer then adds a second, wider limit that applies at the same time, a global cap across every key, so a request is admitted only when it clears both budgets. The draft that stalls keeps one counter and has no room for a second rule. The draft that extends keeps one sliding window per rule and admits only when every window has room, so adding a level means adding a window and one more clause to the and.
Multi-Level Rate Limiter Over a Timestamp Stream
Problem
RateLimiter that decides, for a stream of requests arriving in nondecreasing timestamp order, whether each request is admitted or denied.The constructor takes
per_key_limit, global_limit, and window. allow(key, timestamp) returns true if the request is admitted and false if it is denied.A request is admitted only when both limits have room. That means at most
per_key_limit admitted requests for that key within the last window units of time, and at most global_limit admitted requests across all keys within the same window. A window is the half-open span (timestamp - window, timestamp], so a request made exactly window units after an earlier one no longer counts that earlier one. Only admitted requests consume budget; a denied request records nothing.Input: RateLimiter(2,3,10); allow("a",1); allow("a",2); allow("a",3); allow("b",4); allow("b",5); allow("a",11)
Output: true, true, false, true, false, true
Key "a" fills its two slots at t=1 and t=2, so t=3 is denied. "b" at t=4 fills the third global slot. "b" at t=5 is denied because the global limit of 3 is full. By t=11 the t=1 event has left the window, freeing both an "a" slot and a global slot, so "a" at t=11 is admitted.
Input: RateLimiter(5,2,100); allow("a",1); allow("b",1); allow("c",1); allow("a",2)
Output: true, true, false, false
The per-key limit is loose, but the global limit of 2 fills at t=1 across keys "a" and "b". The third and fourth requests are denied even though each key is under its own budget.
Keep one sliding window of admitted timestamps per key and one shared window for the global count. On each request, evict timestamps at or before timestamp minus window from both. Admit only when both windows are below their limits, and on admit append the timestamp to both.
Multi-Level Rate Limiter Over a Timestamp Stream
Click "Run" to execute your code
Test Cases
[["RateLimiter",[2,3,10]],["allow",["a",1]],["allow",["a",2]],["allow",["a",3]],["allow",["b",4]],["allow",["b",5]],["allow",["a",11]],["allow",["b",12]]]Expected: [null,true,true,false,true,false,true,true][["RateLimiter",[1,5,1]],["allow",["x",1]],["allow",["x",1]],["allow",["x",2]],["allow",["y",2]]]Expected: [null,true,false,true,true][["RateLimiter",[5,2,100]],["allow",["a",1]],["allow",["b",1]],["allow",["c",1]],["allow",["a",2]]]Expected: [null,true,true,false,false]What these three share
Look across the three and the same choice wins every time. The store, the iterator, and the limiter each have a naive base that passes the opening prompt and cannot take the extension. Each also has a base built from separated state that takes the extension in a few lines. That means a stack of overlays over a boolean, explicit position fields over a generator, and one window per rule over one counter. Rather than memorizing these three solutions, train the reflex to ask, before you write the base, where the requirement is most likely to move and which structure keeps that move cheap.
Common questions
What is the most-cited xAI coding problem?
The in-memory key-value store with transactions. It opens with set and get, then grows begin, commit, and rollback. The extension that decides the round is nested transactions, where a begin inside an open transaction commits into its parent or rolls back on its own. Model the transaction state as a stack of overlay dictionaries instead of a single flag. Nesting then becomes one more push, while a flag-based draft has to be rewritten.
How do I keep a base extensible under a live follow-up?
Keep the piece of state the follow-up will touch in its own structure from the first line, and build the base out of small, independent operations. That state is a stack of transaction overlays in the store, an explicit read position held as plain fields in the iterator, and one sliding window per rule in the limiter. When the extension lands, ask what it changes about that state, then make the smallest structural addition instead of rewriting the whole component.
Do these problems have to be in a particular language?
No. Use the language you are strongest in, since the round is language-agnostic and Python, C++, Rust, and Go all appear. The structures translate directly: the overlay stack is a list of dictionaries, the iterator position is two integers, and each rate-limit window is a queue. Pick the language where you can write small correct operations fastest, because the graded signal is clean extensible code you can defend and a language-specific trick adds little.
How should I test these while I build?
Run a small sequence of operations against each component as soon as the base works, and name the edge cases yourself before the interviewer does. In the store, check that a rollback restores a deleted key, and in the iterator, that a snapshot taken mid-run resumes at the right value. In the limiter, check the exact window boundary where an old event ages out. The xAI bar rewards testing as you go, and it catches the off-by-one that would otherwise surface only when the extension leans on it.
Key Takeaways
- ✓The xAI coding round is implement-then-extend. Build a working base, then absorb the feature or tighter constraint the interviewer adds live. Choose the base structure for the follow-up you cannot see yet, because at xAI it is almost always coming.
- ✓The key-value store grows nested transactions. Model the transaction state as a stack of overlay dictionaries, one per open transaction, so nesting is a push and rollback is a pop. Represent an in-transaction delete as a tombstone so rollback can restore the shadowed value.
- ✓The stateful iterator gains checkpoint and resume. Hold the read position as explicit fields, the run index and the offset within it, instead of burying progress in a generator or a decompressed list. The get_state call then copies two integers out and set_state writes them back.
- ✓The multi-level rate limiter goes from one key to compound rules. Keep one sliding window per rule and prune the aged-out events. Admit only when every window has room, and record on admit into all windows. A second or third level is then one more window and one more clause.
The next lesson and 3 more xAI lessons are Premium. $49 a year, less than $1 a week · Cancel anytime
xAI interview guideKnow what xAI asks before you walk in.
8 coding · 2 system design · 15 behavioral questions
Get their questions first.
InterviewLegend is independent and not affiliated with or endorsed by any company it covers.