Katkat

Account Balance with Expiring Grants

AnthropicSoftware Engineer · Onsite

Last updated

Design a data structure that tracks time-limited credit grants, prioritizes consuming grants that expire earliest, and efficiently manages expired funds.

Problem Statement

Design an account balance management system that tracks promotional credit grants. Each grant has a credit amount and an expiration timestamp. When a user spends credits, the system must consume credits that expire the earliest to minimize waste. Credits that have expired cannot be spent and do not count toward the active balance.

A grant is considered expired at current_time if expire_at None: Adds a new grant with a positive amount that expires at timestamp expire_at.

  • spend(amount: int, current_time: int) -> bool: Attempts to deduct amount credits at current_time. If the total active (unexpired) balance is at least amount, deducts the credits prioritizing the earliest expiration date and returns True. If there are not enough active credits, makes no deductions and returns False.
  • get_balance(current_time: int) -> int: Returns the total amount of active (unexpired) credits at current_time.

Constraints

  • 1 <= amount <= 10^9 for both add_grant and spend.
  • 1 <= expire_at, current_time <= 10^9.
  • Calls to spend and get_balance will be made with monotonically non-decreasing current_time values.
  • For any add_grant call, expire_at is strictly greater than the most recent current_time seen in any previous spend or get_balance call.
  • At most 10^5 total calls across all methods.

Example

tracker = CreditTracker()
tracker.add_grant(100, 10)  # Grant 1: 100 credits expiring at t=10
tracker.add_grant(50, 20)   # Grant 2: 50 credits expiring at t=20

tracker.get_balance(5)      # Returns 150 (both grants valid at t=5)

tracker.spend(70, 7)        # Returns True. Deducts 70 from Grant 1. Grant 1 has 30 left.
tracker.get_balance(8)      # Returns 80 (30 from Grant 1 + 50 from Grant 2)

tracker.get_balance(10)     # Returns 50 (Grant 1 expired at t=10, 50 left from Grant 2)

tracker.spend(60, 12)       # Returns False (only 50 active credits; no deduction made)
tracker.spend(40, 15)       # Returns True (deducts 40 from Grant 2, leaving 10)
tracker.get_balance(20)     # Returns 0 (Grant 2 expired at t=20)

Edge Cases

  • Spending exact balance: Deduction should empty grants cleanly without leaving zero-amount entries.
  • Insufficient funds: The operation must be atomic—no credits may be deducted if the full amount cannot be fulfilled.
  • Multiple grants expiring at the exact same timestamp.

Hints

Hint 1

Use a min-heap keyed by expiration timestamp to quickly access the grant that expires soonest.

Hint 2

Maintain a running total of active credits so that `get_balance` can be fast, updating it when adding grants, expiring grants, or spending.

Hint 3

Since timestamps are non-decreasing, you can lazily purge all grants with `expire_at <= current_time` from the heap before spending or querying the balance.

Follow-up questions

  • How would you support a `refund(amount, refund_time)` operation that restores spent credits, and which grant's expiration time should be prioritized?
  • How would you adapt the design if queries and spends could arrive out of order (i.e., non-monotonic timestamps)?
  • How would you scale this design to handle concurrent spends from multiple workers across millions of user accounts?