Katkat

Spreadsheet Formula Engine with Cycle Detection

HarveySoftware Engineer · Technical Screen

Last updated

Design an in-memory spreadsheet formula engine supporting cell assignments, formula evaluation with dependencies, and cycle detection. The question tests directed graph modeling, topological invalidation, and cycle detection algorithms.

Problem Statement

Implement an in-memory spreadsheet formula engine. The spreadsheet consists of cells identified by alphanumeric string IDs (e.g., "A1", "B2"). Cells can hold either integer literals or formulas that sum dependencies and integer constants.

Implement the Spreadsheet class with the following methods:

  • set_cell(cell_id: str, expr: str) -> bool: Sets the contents of cell_id to expr. An expression is either:

    • An integer literal as a string (e.g., "10", "-3").
    • A formula starting with "=", consisting of cell IDs and/or integer literals separated by "+" without spaces (e.g., "=A1+B2", "=A1+5").

    If setting this expression would introduce a circular dependency (directly or indirectly), the update must be rejected: return False and preserve the cell's previous value. Otherwise, apply the update and return True.

  • get_cell(cell_id: str) -> int: Returns the evaluated integer value of cell_id. If the cell has never been set, its default value is 0.


Class Interface

class Spreadsheet:
    def __init__(self):
        pass

    def set_cell(self, cell_id: str, expr: str) -> bool:
        pass

    def get_cell(self, cell_id: str) -> int:
        pass

Examples

Example 1

sheet = Spreadsheet()
sheet.set_cell("A1", "10")      # returns True
sheet.set_cell("B1", "20")      # returns True
sheet.set_cell("C1", "=A1+B1")  # returns True
sheet.get_cell("C1")            # returns 30

sheet.set_cell("A1", "15")      # returns True (updates A1)
sheet.get_cell("C1")            # returns 35 (reflects updated A1)

Example 2 (Cycle Detection)

sheet = Spreadsheet()
sheet.set_cell("A1", "=B1+1")   # returns True
sheet.set_cell("B1", "=C1+2")   # returns True
sheet.set_cell("C1", "=A1+3")   # returns False (creates cycle: A1 -> B1 -> C1 -> A1)
sheet.get_cell("C1")            # returns 0 (reverted/never set)

Constraints

  • cell_id consists of 1 to 5 uppercase letters followed by 1 to 5 digits.
  • Integer literals fit within a standard 32-bit signed integer range.
  • Up to 10,000 calls combined across set_cell and get_cell.
  • Expressions in formulas only use the "+" operator and contain no whitespace.

Hints

Hint 1

Model the spreadsheet as a directed dependency graph where an edge exists from cell A to cell B if A depends on B.

Hint 2

Before committing an update in `set_cell`, run a cycle detection check (e.g., DFS or BFS) starting from the proposed dependencies to see if they can reach `cell_id`.

Hint 3

Decide whether to recompute cell values eagerly on each `set_cell` call using topological sorting or lazily on `get_cell` using memoization with cache invalidation.

Follow-up questions

  • How would you optimize the engine if the workload is 99% reads (`get_cell`) versus if it is 99% writes (`set_cell`)?
  • How would you support range expressions like `=SUM(A1:A1000)` without creating 1,000 individual dependency edges?
  • How would you handle concurrent reads and writes safely in a multi-threaded server environment?