InterviewLegend

Stateful Simulation & OOD

Alex Chen30 min readUpdated 63 real interview reports
In this lesson
  • Recognize the Optiver online assessment as a dense-spec simulation where you build a class that answers queries
  • Use a four-step method that handles every reskin of the problem
  • Drill three runnable versions: a single-symbol order book, a multi-core overheat controller and a truck-position feed

The Optiver software-engineer assessment does not look like the coding rounds at most firms. There is no pattern to spot and no clever recurrence to reach for. Instead you get a long, specific story: a bank of CPU cores that overheat, a warehouse of trucks reporting positions, an order book taking bids and offers. You build a class whose methods consume a stream of instructions and produce ordered answers. The whole test is whether you can hold a moving system correct in your head and translate a dense spec into clean state, one method at a time.

That makes it a different skill from the usual grind. The algorithms underneath are ordinary: a hash map here, a heap there, a running counter. The bookkeeping is hard. You track exactly what changes on each instruction and decide what to store so every query stays cheap. The twentieth event must not quietly corrupt the state the first one set up. Learn the method for that and the whimsical wrapper stops mattering, because Optiver reskins the same underlying shape over and over.

How Optiver frames it

The flagship of the online assessment is one or two of these object-oriented simulation problems, delivered on HackerRank and given the bulk of the roughly two-hour window. A CS-knowledge multiple-choice block, a battery of timed cognitive mini-games, and sometimes a light probability section sit around it, but the simulation is the part that decides the coding verdict. The prompt arrives as a narrative with a fixed set of operations, and you fill in a provided class stub. C++, Java and Python are all accepted, so pick the one you know most deeply.

The stories rotate but the family is stable. Public interview accounts describe a multi-core overheat controller that the assessment leans on heavily right now. Others describe a balloon or exhibition tracker that wants the largest item currently present, and a single-symbol order book that adds, cancels and matches. There is also a publish-subscribe feed that streams ordered deltas to a subscriber under a delivery limit. Treat the surface as noise and the shape as the signal. Every one of them is a class that swallows an instruction stream and answers queries about the state that stream builds.

The Optiver angle
The Optiver name is famous for a mental-math sprint, eighty questions in eight minutes, but that test gates traders, not engineers. The software-engineer loop routes through this simulation instead, and the firm's own hiring material is explicit that it cares how you think, not whether you have memorized a market. So point your prep at the right target. The pass comes from state management under a spec that keeps adding rules, and from parsing that spec accurately. Name the corner cases before the hidden tests find them. The assessment shows almost no sample cases, so you debug largely blind. Clever one-liners score nothing. Correct, legible bookkeeping scores everything.

Designing the stateful class

Four moves crack every reskin. They are boring on purpose, because the assessment rewards discipline over inspiration. Run them in order on any of these problems and the class falls out.

Move 1: Read the spec for the state

Before writing a line, list every noun the spec tracks and every fact a query can ask about. Those nouns are your fields. If the prompt talks about trucks and their positions, you hold a position per truck. If it talks about cores that heat and throttle, you hold a temperature and a throttled flag per core. The trap is storing the raw instruction log and recomputing answers on demand, which turns a cheap query into a linear scan. Store the derived state the queries need, and update it as events arrive.

Move 2: One method per instruction, one job each

Each instruction in the stream maps to one method, and each method changes the state in exactly one well-defined way. Keep the mutations small and named. When an order arrives you match it, then rest the remainder. When a tick fires you age every core by one step. Resist folding two behaviors into one method to save lines, because that is where a stray edge case hides. The skeleton below is the shape every one of these problems takes.

python
class Simulation:
def __init__(self, *config):
self.state = ... # the fields Move 1 identified
def instruction(self, *args):
# mutate state in one clear way; return nothing, or a value if the spec asks
...
def query(self, *args):
# read an answer straight off the state, no recomputation from a log
return ...

Move 3: Keep each query at the complexity the spec implies

A query that runs often must be cheap, and the spec usually tells you how cheap by how many times it is called. A best-price query on an order book taking thousands of updates cannot afford a full scan, so you keep the best price maintained as orders move, or you reach for a heap. A largest-currently-present query wants a max-heap with lazy deletion, so removals in the middle of the stream do not force a rescan. Match the structure to the hottest query and the rest of the class is just careful updates.

Every one of these problems reduces to a single discipline: name the state, prove it stays valid after each instruction, and read every query straight off it. The order book holds resting orders indexed for fast best-price lookup, so a match is a walk down one side and a query is a peek. The overheat controller holds a temperature and a throttle flag per core, so a tick is one clamped update and a query is a read. The truck feed holds a delta log per truck and a per-subscriber cursor, so a poll is a bounded slice. Decide what you carry, keep it true as the stream advances, and the answers are already computed when the query lands.

Asked at Optiver

Three problems, each a faithful reskin of an Optiver assessment. They use the class-operations format. The first entry constructs the class and each later entry calls a method. The expected array holds every call's return in order, with a null for the constructor and for methods that return nothing. Write the class in the editor, run it, then read the walkthrough, which carries a harder variant the rest of the way.

1

CPU Overheat Controller: A Thermal State Machine

MediumSignature problem
Problem
Implement the thermal controller for a multi-core CPU. There are num_cores cores, indexed 0 to num_cores - 1, each starting at temperature 0 with no load and not throttled. Two constants govern behavior: an overheat threshold and a per-tick cooldown.

- set_load(core, load): set the ongoing load on a core, an integer number of degrees it heats per tick. It persists until changed.
- tick(): advance one time step. For each core, if it is throttled it cools by cooldown degrees, clamped so temperature never drops below 0; the instant a throttled core reaches 0 it stops throttling. If it is not throttled it heats by its current load, and if its temperature then reaches or exceeds threshold it becomes throttled. Return the number of cores throttled after the tick.
- temperature(core): return a core's current temperature.
- overheated(): return the ascending list of throttled core ids.

Input encoding. The class-operations format: the first entry is ["CPUController", [num_cores, threshold, cooldown]], and each later entry is a call like ["set_load", [0, 3]], ["tick", []], or ["overheated", []]. The expected array is each call's return in order, with null for the constructor and for set_load.

Input: CPUController(1,5,5); set_load(0,5); tick(); tick(); temperature(0)

Output: [null, null, 1, 0, 0]

The first tick heats core 0 to 5, hitting the threshold, so it throttles and the count is 1. The second tick cools it by 5 to 0, which un-throttles it, so the count is 0 and its temperature reads 0.

How this uses the pattern

Hold a temperature, a load, and a throttled flag per core in parallel arrays. A tick is one pass over the cores: throttled cores cool and may un-throttle at zero, others heat and may cross the threshold. The two queries read a single core or collect the throttled ids.

CPU Overheat Controller: A Thermal State Machine

Loading...
Click "Run" to execute your code

Test Cases

Input: [["CPUController",[2,10,4]],["set_load",[0,3]],["set_load",[1,6]],["tick",[]],["tick",[]],["temperature",[1]],["overheated",[]],["tick",[]],["tick",[]],["overheated",[]],["tick",[]],["temperature",[1]],["overheated",[]]]Expected: [null,null,null,0,1,12,[1],1,2,[0,1],1,0,[0]]
Input: [["CPUController",[1,5,5]],["set_load",[0,5]],["tick",[]],["tick",[]],["temperature",[0]],["tick",[]]]Expected: [null,null,1,0,0,1]
Input: [["CPUController",[3,100,10]],["tick",[]],["tick",[]],["overheated",[]],["temperature",[2]]]Expected: [null,0,0,[],0]
Input: [["CPUController",[1,20,3]],["set_load",[0,4]],["tick",[]],["tick",[]],["set_load",[0,10]],["tick",[]],["tick",[]],["temperature",[0]],["set_load",[0,0]],["tick",[]]]Expected: [null,null,0,0,null,0,1,28,null,1]
Input: [["CPUController",[2,6,2]],["set_load",[0,2]],["set_load",[1,3]],["tick",[]],["tick",[]],["tick",[]],["overheated",[]],["temperature",[0]]]Expected: [null,null,null,0,1,2,[0,1],6]
The Optiver angle
This overheat controller is the simulation Optiver assessments lean on most heavily at the moment, and it is a clean test of a state machine per element. Each core is independently either heating toward a limit or cooling back down, and a tick has to age all of them by exactly one step without letting one core's transition leak into another. The grading is about getting the two directions right, the clamp at zero and the resume rule when a throttled core cools all the way down, not about any algorithmic trick. Reports frame these as puzzle-box problems where the difficulty is holding a lot of small rules straight at once.
Take it further
A natural query the spec grows into is prediction: how many ticks until a given core overheats at its current load, assuming it is not already throttled. With load L, current temperature t, and threshold T, a core needs its temperature to reach T, so the answer is the smallest whole number of ticks k with t + k*L >= T. A load of zero never overheats, so it returns a sentinel. The walkthrough derives the closed form and shows why simulating the ticks is unnecessary.
2

Single-Symbol Order Book: Add, Cancel, Match

MediumFrequently asked
Problem
Implement a limit order book for a single symbol. Orders rest at a price until they trade. Support four operations:

- add(order_id, side, price, quantity): submit a limit order. side is "buy" or "sell". Before resting, the order matches against the opposite side while it crosses. A buy trades against resting sells priced at or below its price, cheapest first. A sell trades against resting buys priced at or above its price, highest first. Ties at the same price go to the earliest resting order. Each match fills the smaller of the two remaining quantities. Any unfilled remainder rests in the book. Return the total quantity this order filled on submission.
- cancel(order_id): remove the resting remainder of that order and return the quantity cancelled, or 0 if the order is unknown or already fully filled.
- best_bid(): return the highest resting buy price, or -1 if there are no resting buys.
- best_ask(): return the lowest resting sell price, or -1 if there are no resting sells.

Order ids are unique. Prices and quantities are positive integers.

Input encoding. The class-operations format: the first entry is ["OrderBook", []], and each later entry is a call like ["add", [1, "buy", 100, 10]], ["cancel", [1]], or ["best_bid", []]. The expected array is each call's return in order, with null for the constructor.

Input: OrderBook(); add(1,"buy",100,10); add(2,"sell",100,4); best_bid()

Output: [null, 0, 4, 100]

The buy rests since nothing sells at or below 100. The sell then trades 4 against it, filling 4 and leaving the buy with 6 resting, so best_bid is still 100.

How this uses the pattern

Hold every resting order in a map from id to its side, price, remaining quantity, and arrival sequence. On add, repeatedly find the best crossable order on the opposite side and fill against it until the incoming order stops crossing or is exhausted, then rest any remainder. The best-price queries read the extreme resting price on each side.

Single-Symbol Order Book: Add, Cancel, Match

Loading...
Click "Run" to execute your code

Test Cases

Input: [["OrderBook",[]],["add",[1,"buy",100,10]],["add",[2,"buy",99,5]],["best_bid",[]],["best_ask",[]],["add",[3,"sell",101,8]],["best_ask",[]],["add",[4,"sell",100,4]],["best_bid",[]],["add",[5,"sell",99,20]],["best_bid",[]],["best_ask",[]],["cancel",[3]],["best_ask",[]],["cancel",[99]]]Expected: [null,0,0,100,-1,0,101,4,100,11,-1,99,8,99,0]
Input: [["OrderBook",[]],["add",[1,"sell",50,10]],["add",[2,"buy",50,10]],["best_bid",[]],["best_ask",[]]]Expected: [null,0,10,-1,-1]
Input: [["OrderBook",[]],["add",[1,"buy",10,5]],["add",[2,"sell",8,3]],["best_bid",[]],["best_ask",[]],["add",[3,"sell",8,10]],["best_ask",[]],["best_bid",[]]]Expected: [null,0,3,10,-1,2,8,-1]
Input: [["OrderBook",[]],["add",[1,"buy",100,5]],["cancel",[1]],["best_bid",[]],["add",[2,"buy",100,5]],["add",[3,"buy",101,5]],["best_bid",[]]]Expected: [null,0,5,-1,0,0,101]
Input: [["OrderBook",[]],["add",[1,"sell",20,5]],["add",[2,"sell",20,5]],["add",[3,"buy",20,6]],["cancel",[1]],["cancel",[2]],["best_ask",[]]]Expected: [null,0,0,6,0,4,-1]
The Optiver angle
The order book is the trading-domain reskin Optiver reaches for, and it is the one that rewards knowing the real mechanics: price-time priority, partial fills, and a resting remainder. The assessment tests whether you can hold the book correct while add both trades and rests in a single call. Get the priority rule exactly right, cheapest ask and highest bid first with earliest arrival breaking ties, and keep best_bid and best_ask reading cleanly off the resting orders. The reward for correctness here is a follow-up on making the best-price query sub-linear, which is where the heap comes in.
Take it further
The realistic escalation adds a market order: fill against the book at any price, ignoring the crossing check. A market buy takes the cheapest asks in turn until its quantity is met or the sell side is empty, and it never rests. Return the list of (price, quantity) fills so the caller sees the execution. It reuses the same best-opposite search the limit path already has, dropping only the price guard. The walkthrough sketches it and shows why the resting-remainder step is the part that disappears.
3

Truck Positions: A Publish-Subscribe Delta Feed

MediumFrequently asked
Problem
Implement a position feed for a fleet of trucks. Trucks report their absolute position over time, and a subscriber follows one truck, receiving the changes since it last checked in, delivered a few at a time.

The feed is constructed with a delivery limit, the most deltas any single poll may return.

- update(truck_id, position): record a truck's new absolute position. Its first ever position is measured against a starting point of 0. Each update appends the change from the previous position to that truck's delta log.
- subscribe(truck_id): begin following a truck. Return its current position, or 0 if it has never reported. The subscriber will only receive deltas that occur after this call.
- poll(truck_id): return the next batch of unseen deltas for a subscribed truck, in order, at most limit of them, and advance the subscriber past what it returns. Return an empty list if there is nothing new, or if the truck was never subscribed.

Input encoding. The class-operations format: the first entry is ["TruckFeed", [limit]], and each later entry is a call like ["update", [7, 100]], ["subscribe", [7]], or ["poll", [7]]. The expected array is each call's return in order, with null for the constructor and for update.

Input: TruckFeed(2); update(7,100); subscribe(7); update(7,130); update(7,125); poll(7)

Output: [null, null, 100, null, null, [30, -5]]

The subscribe returns the current position 100 and marks the baseline, so the earlier update is not replayed. The next two updates append deltas of 30 and -5, and the poll returns both since the limit is 2.

How this uses the pattern

Keep, per truck, its current position and an append-only log of deltas. Keep a cursor into that log for each subscribed truck. On subscribe, set the cursor to the current end so only future deltas are seen. On poll, return the slice from the cursor of length up to the limit and advance the cursor.

Truck Positions: A Publish-Subscribe Delta Feed

Loading...
Click "Run" to execute your code

Test Cases

Input: [["TruckFeed",[2]],["update",[7,100]],["update",[7,130]],["subscribe",[7]],["poll",[7]],["update",[7,125]],["update",[7,140]],["update",[7,141]],["poll",[7]],["poll",[7]],["poll",[7]]]Expected: [null,null,null,130,[],null,null,null,[-5,15],[1],[]]
Input: [["TruckFeed",[3]],["subscribe",[1]],["poll",[1]],["update",[1,10]],["update",[1,7]],["poll",[1]]]Expected: [null,0,[],null,null,[10,-3]]
Input: [["TruckFeed",[2]],["update",[5,50]],["poll",[5]],["subscribe",[5]],["poll",[5]],["update",[5,60]],["poll",[5]]]Expected: [null,null,[],50,[],null,[10]]
Input: [["TruckFeed",[1]],["subscribe",[2]],["update",[2,5]],["update",[2,9]],["update",[2,2]],["poll",[2]],["poll",[2]],["poll",[2]],["poll",[2]]]Expected: [null,0,null,null,null,[5],[4],[-7],[]]
Input: [["TruckFeed",[5]],["update",[1,10]],["update",[2,20]],["subscribe",[1]],["subscribe",[2]],["update",[1,15]],["update",[2,18]],["poll",[1]],["poll",[2]]]Expected: [null,null,null,10,20,null,null,[5],[-2]]
The Optiver angle
This truck feed is the publish-subscribe reskin, the same shape reports describe as a news or message subscription engine: a subscriber asks for updates and receives them in order, capped per delivery. What makes it a good state exercise is the cursor. A subscriber that joins late must not be handed the whole history, and a poll must never re-deliver what it already returned, so the position of that cursor is the entire correctness story. The delivery limit adds the small twist that one poll may not drain the backlog, so the caller polls again to keep draining.
Take it further
The realistic extension is multiple independent subscribers on the same truck, each with its own view. Instead of one cursor per truck you keep a cursor per subscriber, so two subscribers that joined at different times drain the same delta log from different offsets and never interfere. The delta log stays shared and append-only; only the cursors multiply. The walkthrough shows the small change from a per-truck cursor to a per-subscriber one.

Key Takeaways

  • ✓The Optiver software-engineer assessment is a stateful object-oriented simulation. You implement a class that consumes an instruction stream and answers queries, and the grading is state management, spec parsing, and edge cases, debugged on hidden tests with almost no visible samples.
  • ✓Four steps handle every reskin. Read the spec for the state you must hold, and store derived state over a raw log. Map one method to each instruction. Keep every query at the complexity its call frequency implies.
  • ✓The named problems are one shape in different costumes. An order book indexes resting orders for price-time matching and best-price reads. An overheat controller runs a two-state thermal machine per core. A truck feed keeps a delta log per truck with a per-subscriber cursor.
  • ✓The famous eighty-in-eight math sprint gates traders, not engineers, so point software-engineer prep at this simulation. Name the corner cases before the hidden cases find them: the zeroed order removed from the book, the temperature clamped at zero, the cursor advanced by what was delivered.
Had your Optiver interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Optiver interview guide

Know what Optiver asks before you walk in.

6 coding · 1 system design · 19 behavioral questions

Get Premium$49 a year · Cancel anytime
Or start freeNo card.

Get their questions first.

InterviewLegend is independent and not affiliated with or endorsed by any company it covers.