Account Balance with Expiring Grants
AnthropicSoftware Engineer · OnsiteLast 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 deductamountcredits atcurrent_time. If the total active (unexpired) balance is at leastamount, deducts the credits prioritizing the earliest expiration date and returnsTrue. If there are not enough active credits, makes no deductions and returnsFalse.get_balance(current_time: int) -> int: Returns the total amount of active (unexpired) credits atcurrent_time.
Constraints
1 <= amount <= 10^9for bothadd_grantandspend.1 <= expire_at, current_time <= 10^9.- Calls to
spendandget_balancewill be made with monotonically non-decreasingcurrent_timevalues. - For any
add_grantcall,expire_atis strictly greater than the most recentcurrent_timeseen in any previousspendorget_balancecall. - At most
10^5total 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
amountcannot 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?
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.