Reported September 2026
Optiversimulation

Power Cell Bank

Reported by candidates from Optiver's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.

Get StealthCoderRuns invisibly during the live Optiver OA. Under 2s to a working solution.
Founder's read

The edge case that kills a naive Power Cell Bank solution is the spent cell. It looks gone but still sits in your racks, and if you count it toward capacity, a later load_cell returns False when it should succeed. Optiver candidates reported this one in September 2026. It's a design and simulation problem: racks with capacity 2^i, three temporal states, and a discharge that runs equalisation before dispatch. The spec is long and the details matter. If you blank on the equalisation rules during the live OA, StealthCoder is the safety net that reads the problem and hands you a working structure.

The problem

ᨳଓ A big thank you to the friend who shared this problem with FastPrep! ᢉ𐭩
You are writing dispatch logic for a battery-management system that powers a heavy-duty industrial load. Cells enter a tiered power-cell bank through load_cell, and discharge draws batches for the load. Unused cells self-discharge from charged through depleted to spent, and adjacent racks are equalised before each dispatch batch to keep output voltage stable.
Implement the PowerCellBank class.
The bank contains num_racks racks arranged from front to back. Rack 1 is the front rack, and rack i has capacity 2^i.
A cell is installed with a load timestamp and a rated duration. As time passes, each cell automatically moves through three temporal states: charged, depleted, and spent.
Class Interface
class PowerCellBank:
def __init__(self, num_racks: int) -> None:
...
def load_cell(
self,
timestamp: float,
cell_id: str,
rated_duration: float,
) -> bool:
...
def discharge(
self,
timestamp: float,
max_dispatch: int,
) -> list[str]:
...
Constructor
__init__(num_racks: int) -> None
Initializes an empty bank containing num_racks racks. The racks are numbered from 1 to num_racks. Rack 1 is the front rack, and rack i has capacity 2^i.
For example, with three racks, the capacities are:
rack 1: 2
rack 2: 4
rack 3: 8
Loading A Cell
load_cell(
timestamp: float,
cell_id: str,
rated_duration: float,
) -> bool
Installs one freshly charged cell into the bank. The cell is placed in the frontmost rack that still has free capacity.
Return:
True if the cell was installed successfully.
False if every rack is full.
A spent cell is treated as absent, so it does not occupy rack capacity, its slot may be reused, and its cell_id may be reused by a later load_cell call.
You may assume:
rated_duration > 0.
No non-spent cell currently in the bank has the same cell_id as the new cell.
A dispatched cell is also removed from the bank, so its ID may later be reused.
Temporal States
For a cell loaded at load_timestamp, define t = timestamp - load_timestamp.
The cell's state at the current operation timestamp is:
StateElapsed timeDispatchable?Occupies a slot?Output label
Chargedt < rated_durationYesYes"charged"
Depletedrated_duration <= t < 2 * rated_durationYesYes"depleted"
Spentt >= 2 * rated_durationNoNonone
State changes happen automatically as time passes. No explicit update is required at the transition moment.
Spent cells are treated as absent everywhere:
They are not dispatched.
They do not occupy rack capacity.
They are excluded from all counts.
They are excluded from charge equalisation.
They are excluded from bus-shift selection.
They do not appear in output.
Throughout the specification, cell means a non-spent cell unless stated otherwise.
Charge Ordering
At a call's timestamp, define a cell's charge window as load_timestamp + rated_duration - timestamp.
This value is positive for charged cells and zero or negative for depleted cells. Cells are ordered by charge window.
The most-charged cell is the cell with the largest charge window.
The most-depleted cell is the cell with the smallest charge window.
When charge windows tie, choose the cell with the lexicographically smallest cell_id using ordinary ASCII comparison. For example, "a" < "b". The same tie-breaking rule applies to both most-charged and most-depleted selection.
Discharging Cells
discharge(
timestamp: float,
max_dispatch: int,
) -> list[str]
The operation has two stages:
Run one charge-equalisation cycle.
Dispatch up to max_dispatch cells using repeated bus-shift pulls.
The returned list contains entries in dispatch order. Each entry has the form cell_id:state, where state is "charged" if the cell is still within its rated window at the call's timestamp and "depleted" otherwise.
For example: ["c2:charged", "c7:depleted"].
Dispatching stops early when the bank is empty. You may assume 1 <= max_dispatch <= 2^10.
Charge Equalisation
Charge equalisation runs once at the start of every discharge call.
A rack is below-sag when it contains at least one cell and strictly fewer than 50% of its cells are charged. Equivalently, 2 * charged_cell_count < total_cell_count.
The equalisation cycle visits racks from front to back. For each rack k that has a rear neighbour k + 1, while both of the following conditions hold:
rack k is below-sag;
rack k + 1 contains at least one charged cell;
perform one balance swap:
Choose rack k's most-depleted cell.
Choose rack k + 1's most-charged charged cell.
Swap the two cells.
Continue swapping until rack k is no longer below-sag or rack k + 1 has no charged cells remaining. Then move on to rack k + 1.
The final rack has no rear neighbour, so no equalisation begins from it.
Bus Shift
Each bus-shift pull dispatches exactly one cell through two actions.
1. Dispatch
Find the active rack, defined as the frontmost rack containing at least one cell. Dispatch the most-charged cell from that rack. When charge windows tie, dispatch the cell with the lexicographically smallest cell_id.
If no active rack exists, the bank is empty and the discharge call stops.
2. Inward Shift
The dispatched cell frees one slot in the active rack. Fill that slot by moving the most-charged cell from the next rack back into the active rack.
That movement frees one slot in the next rack, so fill it by moving the most-charged cell from the rack behind it. Continue propagating the vacancy toward the back.
The shift stops when the next rack is empty or there is no further rack.
A cell moved into a rack during the inward shift is immediately eligible for dispatch during the next pull.
FastPrep Runner Adapter
The executable FastPrep interface represents calls to the class as one operation stream without changing any bank behavior:
powerCellBank(int numRacks, String[][] operations) -> String[][]
Operations arrive in non-decreasing timestamp order and are processed in the order supplied. When multiple operations share a timestamp, preserve their input order.
Process each operation and return one row for every operation:
["load_cell", timestamp, cell_id, rated_duration]: call load_cell and append ["true"] or ["false"].
["discharge", timestamp, max_dispatch]: call discharge and append its returned list. This row may be empty.
All values inside an operation row are strings. Parse timestamps and rated durations as floating-point numbers and max_dispatch as an integer.

Function
powerCellBank(numRacks: int, operations: String[][]) → String[][]

Examples
Example 1
numRacks = 2
operations = [["load_cell","0","a","4"],["load_cell","0","b","5"],["load_cell","2","c","10"],["load_cell","2","d","10"],["discharge","6","3"],["discharge","9","2"],["load_cell","9","a","2"]]
return = [["true"],["true"],["true"],["true"],["c:charged","d:charged","b:depleted"],[],["true"]]
At timestamp 6, cells a and b are depleted in rack 1, while c and d are charged in rack 2. Equalisation swaps a, the most-depleted front cell, with c, the lexicographically first of the tied rear cells. The three bus-shift pulls then dispatch c, d, and b.
Cell a remains, but it is spent by timestamp 9, so the next discharge returns an empty row. Its ID can then be loaded again.
Example 2
numRacks = 1
operations = [["load_cell","0","x","10"],["load_cell","0","y","10"],["load_cell","1","z","10"],["discharge","1","1"],["load_cell","1","z","10"]]
return = [["true"],["true"],["false"],["x:charged"],["true"]]
The only rack has capacity 2, so loading z initially fails. Cells x and y have equal charge windows at timestamp 1, so the ASCII tie-break dispatches x. The freed slot then allows z to be loaded.

Constraints
1 <= numRacks <= 2^4.
rated_duration > 0.
No non-spent cell currently in the bank has the same cell_id as a new cell.
1 <= max_dispatch <= 2^10.
Operations arrive in non-decreasing timestamp order and are processed in the order received. Within a single timestamp, ties are broken by call order.
Timestamps are positive floating-point Unix-epoch seconds with millisecond precision and fit in 32 bits.
rated_duration is a floating-point value with millisecond precision (three decimal places).
State-transition thresholds and the 50% charged-fraction threshold are exact.

Reported by candidates. Source: FastPrep

Pattern and pitfall

This is a simulation problem, not a clever algorithm. The trick is to treat time as lazy. Never run a background update. At every call, compute each cell's state from timestamp minus load_timestamp, and purge anything where t >= 2 * rated_duration before you do anything else. Do that at the top of load_cell and discharge, and the capacity, ID reuse and counting rules mostly fix themselves. The second trap is ordering. Charge window is load_timestamp + rated_duration - timestamp, with ties broken by smallest cell_id in ASCII order, for both most-charged and most-depleted. Get the tie-break wrong and hidden tests fail. Keep the per-rack storage simple and re-sort on demand, since max_dispatch is at most 2^10. Read the equalisation and bus-shift rules twice before coding. If the text overwhelms you mid-assessment, StealthCoder is the hedge that turns the spec into code while you keep your head clear.

Memorize the pattern. If you can't, run StealthCoder. The proctor sees the IDE. They don't see what's behind it.

If this hits your live OA

You can drill Power Cell Bank 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 StealthCoder

Related leaked OAs

⏵ The honest play

You've seen the question. Make sure you actually pass Optiver's OA.

Optiver 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.

Power Cell Bank FAQ

How hard is the Power Cell Bank problem really?+

The logic is easy, the spec is not. Nothing here needs a fancy algorithm, but there are many small rules about states, capacity, ordering and tie-breaks. Most failures come from missing one rule, not from a wrong approach. Budget time to read carefully before writing any code.

What's the main trick for this Optiver OA?+

Compute state lazily from the timestamp on every call and drop spent cells first. Spent cells must not occupy slots, appear in counts, or block cell_id reuse. If you purge at the start of each operation, the rest of the logic stays clean.

How should I handle tie-breaking?+

Sort by charge window, then by cell_id using plain ASCII string comparison. The same rule applies to most-charged and most-depleted selection. Use a tuple key like (window, cell_id) and flip only the window direction when you need the most-charged cell.

Is the simulation pattern still asked in OAs?+

Yes. Class-based design problems with time-driven state show up regularly, and Optiver reported this one in September 2026. They test whether you can follow a long spec exactly, not whether you know a named algorithm.

How do I prepare for this in 48 hours?+

Practice writing small stateful classes with lazy expiry and custom sort keys. Skim the spec, list every rule as a checklist, and test edge cases: full racks, expired cells, reused IDs, tied windows. Don't over-optimise, since the input sizes are small.

Problem reported by candidates from a real Online Assessment. Sourced from a publicly-available candidate-aggregated repository. Not affiliated with Optiver.

OA at Optiver?
Invisible during screen share
Get it