Katkat

Process a Timestamped Workspace Credit Ledger

AirbnbSoftware Engineer · Online Assessment

Last updated

Design and implement an in-memory timestamped credit ledger that supports deposits, transfers, spending with delayed cashback rewards, account merges, balance queries, and spend rankings.

Problem Statement

Design an in-memory credit ledger for corporate workspaces. Workspaces hold integer credit balances and perform various financial transactions over time. Events arrive in non-decreasing order of their timestamp.

Implement the WorkspaceLedger class:

  • deposit(workspace_id: str, amount: int, timestamp: int) -> bool: Adds amount credits to workspace_id. If the workspace does not exist, it is created. Returns True.

  • spend(workspace_id: str, amount: int, cashback_percent: int, delay: int, timestamp: int) -> bool: Deducts amount credits from workspace_id if its available balance at timestamp is at least amount. If successful, schedules a delayed cashback of floor(amount * cashback_percent / 100) credits to mature at timestamp + delay. Returns True on success, or False if the workspace does not exist or has insufficient funds.

  • transfer(from_id: str, to_id: str, amount: int, timestamp: int) -> bool: Transfers amount from from_id to to_id if from_id has sufficient balance at timestamp and both IDs are valid and resolve to different workspaces. Returns True on success, False otherwise.

  • merge(source_id: str, target_id: str, timestamp: int) -> bool: Merges source_id into target_id. All current balance, pending cashback rewards, and historical cumulative spend from source_id are permanently transferred to target_id. Future operations targeting source_id automatically resolve to target_id. Returns False if either workspace does not exist or if both IDs resolve to the same workspace; otherwise returns True.

  • get_balance(workspace_id: str, timestamp: int) -> int: Returns the available credit balance of workspace_id at timestamp (including all cashbacks that matured on or before timestamp). Returns -1 if the workspace does not exist.

  • get_top_spenders(k: int, timestamp: int) -> list[str]: Returns the canonical IDs of the top k workspaces sorted by cumulative amount spent up to and including timestamp in descending order. Break ties by canonical workspace ID in lexicographical ascending order. If fewer than k workspaces have spent credits, return all of them.

Constraints

  • Timestamps across all method calls are non-decreasing: 1 <= timestamp <= 10^9.
  • 1 <= amount <= 10^6
  • 0 <= cashback_percent <= 100
  • 1 <= delay <= 10^6
  • 1 <= k <= 100
  • Up to 50,000 total method calls.
  • All workspace IDs consist of alphanumeric characters (length 1 to 32).

Example

ledger = WorkspaceLedger()
ledger.deposit("team-a", 100, 1)          # True, team-a balance = 100
ledger.spend("team-a", 40, 10, 5, 2)     # True, balance = 60; 4 cashback at t=7
ledger.get_balance("team-a", 4)          # 60 (cashback not yet matured)
ledger.get_balance("team-a", 7)          # 64 (cashback matured)
ledger.deposit("team-b", 50, 8)          # True
ledger.merge("team-a", "team-b", 9)      # True, team-b balance = 64 + 50 = 114
ledger.get_balance("team-a", 10)         # 114 (redirected to team-b)
ledger.get_top_spenders(2, 10)           # ["team-b"]

Hints

Hint 1

Use a Disjoint Set Union (DSU) or alias map to handle account merges and redirect operations to canonical workspace IDs.

Hint 2

Maintain a min-heap or priority queue for pending cashbacks ordered by maturity timestamp, processing matured cashbacks lazily up to the query timestamp.

Hint 3

Track cumulative spending per canonical workspace so that merges combine spend totals in O(1) time while keeping top-k extraction efficient via sorting or a heap.

Follow-up questions

  • How would you support out-of-order event ingestion where timestamps are not strictly non-decreasing?
  • If historical queries could ask for `get_balance` at an arbitrary past timestamp `t < current_time`, how would you restructure your storage model?
  • How would you make this ledger crash-resilient and idempotent in a distributed environment with duplicate event deliveries?