Katkat

Implement a Transactional In-Memory Table

AddeparSoftware Engineer · Technical Screen

Last updated

Design an in-memory tabular data store that supports transactional modifications (create, update, delete, find-and-replace) with commit and rollback capabilities.

Problem Statement

Design an in-memory, relational-style table with a fixed schema of column names and a unique integer row ID. All write operations must occur within an active transaction. The table supports starting a transaction, committing changes, or rolling back to the exact state before the transaction began.

Implement the TransactionalTable class:

  • TransactionalTable(columns: list[str]): Initializes the table with a fixed list of column names.
  • begin() -> bool: Begins a new transaction. Returns True if successfully started, or False if a transaction is already active (nested transactions are not supported).
  • commit() -> bool: Commits all modifications made during the active transaction. Returns True on success, or False if there is no active transaction.
  • rollback() -> bool: Discards all modifications made during the active transaction, reverting the table to its state at the time of begin(). Returns True on success, or False if there is no active transaction.
  • insert_row(row_id: int, values: dict[str, str]) -> bool: Inserts a row with the given row_id. Unspecified columns should default to empty string "". Returns False if no transaction is active, if row_id already exists, or if values contains column names not in the schema.
  • update_cell(row_id: int, column: str, value: str) -> bool: Updates the specified cell. Returns False if no transaction is active, if row_id does not exist, or if column is not in the schema.
  • delete_row(row_id: int) -> bool: Deletes the row with the given row_id. Returns False if no transaction is active or if row_id does not exist.
  • find_and_replace(column: str, target: str, replacement: str) -> int: Replaces all occurrences of target with replacement in the given column. Returns the number of cells modified. Returns -1 if no transaction is active or if column is not in the schema.
  • serialize() -> str: Returns a deterministic CSV-formatted string of the current table state (even outside a transaction). The first line is id,,,... matching the schema order. Subsequent lines contain the rows ordered by row_id in ascending order.

Example

table = TransactionalTable(["name", "department"])

# Modifying outside transaction returns False
table.insert_row(1, {"name": "Alice", "department": "Eng"}) # False

table.begin() # True
table.insert_row(1, {"name": "Alice", "department": "Eng"}) # True
table.insert_row(2, {"name": "Bob", "department": "Eng"})   # True
table.find_and_replace("department", "Eng", "Engineering")   # 2
table.commit() # True

table.begin() # True
table.update_cell(1, "department", "HR") # True
table.delete_row(2)                      # True
table.rollback()                         # True

print(table.serialize())
# Output:
# id,name,department
# 1,Alice,Engineering
# 2,Bob,Engineering

Constraints

  • Number of columns = 1.
  • Only one transaction can be active at any given time.

Hints

Hint 1

You can implement rollback either by snapshotting the committed state upon `begin()` or by recording an undo log (inverse operations) for every mutation.

Hint 2

Be sure to handle validation (checking schema columns and row existence) before making changes so failed operations do not corrupt transaction history.

Hint 3

Using an undo log is generally more memory-efficient than deep-copying the entire table when only a small fraction of rows are modified per transaction.

Follow-up questions

  • How would you extend this design to support nested transactions (savepoints)?
  • How would you adapt this data structure to support concurrent read and write transactions safely (e.g., via multi-version concurrency control)?
  • If `find_and_replace` is called frequently across millions of rows, what indexing strategy would you use to avoid full-table scans?