Spreadsheet Class with Formula Cells, Recalculation and Cycle Detection
HarveySoftware Engineer · Technical ScreenLast updated
Build an in-memory spreadsheet class in three stages: integer cells with set_cell and get_cell, formula cells such as =B1+C1+1 with explicit handling of never-set cells and recomputation on every write, then cycle detection and recomputing only dependent cells. Tests incremental design, evaluation order and dependency graphs.
Implement an in-memory spreadsheet class in stages. The interviewer starts with a simple version and adds requirements after each stage, and the full scope is a fair amount of code for a technical screen, so each stage should be finished and runnable before you move on rather than building the final design in one go.
Cells are addressed by labels such as A1 or C12: a column letter followed by a row number. The class exposes two methods:
class Spreadsheet:
def set_cell(self, label: str, value) -> None: ...
def get_cell(self, label: str) -> int | None: ...
Clarifying Questions
- What is the exact label format: single-letter columns only, or also multi-letter columns such as
AA10? Are labels case-sensitive? - Can a cell be overwritten, including changing a plain number into a formula or the reverse?
- Can values be negative?
Part 1 — Integer cells
set_cell(label, value) stores an integer value in the cell. get_cell(label) returns the stored integer. There are no formulas in this stage, so it is meant to be finished quickly.
What This Part Should Cover
- A storage model keyed by label, and what
get_cellreturns for a label that was never set. - Label validation at the level the interviewer asks for.
- Finishing fast enough to leave time for the later stages.
Part 2 — Formula cells
set_cell now also accepts a formula: a string that starts with =. For example:
set_cell("A1", "=B1+C1+1"): the value ofA1is the value ofB1plus the value ofC1plus 1;set_cell("A1", "=1+2"): a formula made only of integer literals.
get_cell returns the computed integer value of a cell. If a cell has never been set, get_cell must return None or report an error; handle this case explicitly. Circular references such as A1 -> B1 -> A1 will not occur in this stage, and efficient dependency updates are not required: recomputing every previously set cell on each set_cell call, so that get_cell is O(1), is an acceptable approach.
Clarifying Questions for this Part
- Which operators can appear in a formula: only
+, as in the examples, or also-,*and parentheses? - When a formula references a cell that has never been set, is that cell treated as 0, or is the formula's own value undefined (
Noneor an error)? - Can formulas contain whitespace, and how should a malformed formula be reported?
What This Part Should Cover
- Parsing a formula into cell references and integer literals.
- Recomputation on every
set_cellthat evaluates cells in a valid order, so that chains of references produce correct values, with O(1)get_cell. - One consistent rule for never-set cells, both when read directly and when referenced by a formula.
- The cost of recomputing everything on every write.
Part 3 — Cycle detection and targeted recomputation (follow-up)
Formulas may now form circular references, for example set_cell("A1", "=B1") followed by set_cell("B1", "=A1+1"). Add cycle detection, and optimize updates so that set_cell recomputes only the cells that depend, directly or indirectly, on the cell that changed.
Clarifying Questions for this Part
- When a
set_cellcall would create a cycle, should it be rejected with the sheet left unchanged, or accepted with the cells on the cycle reporting an error? - Does a self-reference such as
set_cell("A1", "=A1+1")count as a cycle?
What This Part Should Cover
- Detecting a cycle introduced by the new formula, including a self-reference, before any state changes, with a defined outcome.
- Dependency and reverse-dependency tracking that stays correct when a formula is replaced by another formula or by a number.
- Recomputation limited to the transitive dependents of the changed cell, in an order where each cell's inputs are already final.
- The cost of
set_cellandget_cellcompared with Part 2.
Hints
Hint 1
Plan for the next stage: This stage is a thin wrapper around one data structure. Pick a storage layout that will still work once a cell can hold something other than a number.
Hint 2
Inputs before outputs: If `A1` reads `B1` and `B1` reads `C1`, recomputing the cells in the order they were set can read a stale value. Think about how to guarantee that every input of a cell is final before the cell itself is evaluated.
Hint 3
Keep what was typed: Store the formula separately from its current value: the formula is needed again every time something it references changes.
Hint 4
Edges in both directions: Evaluating a cell needs to know what it reads; propagating a change needs to know who reads it.
Follow-up questions
- How would you support deleting or clearing a cell, and what happens to the formulas that reference it?
- How would you extend the parser to `-`, `*`, parentheses and range functions such as `SUM(A1:A10)`, and how do ranges change the dependency graph?
- If writes far outnumber reads, would you still recompute on `set_cell`, or evaluate lazily on `get_cell`? What would you cache?
- How would you make the class safe to use from several threads at once?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.