Implement an expiring GPU credits ledger
Company: OpenAI
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of time-based resource accounting, design of data structures for expiring balances, atomic update semantics for concurrent operations, and algorithmic time/space complexity within the Coding & Algorithms domain.
Constraints
- 1 <= len(operations) <= 20000
- 0 <= amount <= 10^9
- 0 <= expiry_time, timestamp <= 10^9
- Operations are processed in the given order
- A lot is usable exactly when timestamp < expiry_time
Examples
Input: ([('add', 'u1', 50, 10), ('balance', 'u1', 5), ('subtract', 'u1', 20, 6), ('balance', 'u1', 6)],)
Expected Output: [(50, [(50, 10)]), True, (30, [(30, 10)])]
Explanation: At time 5, the lot is still valid, so the balance is 50. Subtracting 20 at time 6 succeeds, leaving 30 in the same lot.
Input: ([('add', 'alice', 10, 5), ('add', 'alice', 30, 20), ('subtract', 'alice', 10, 5), ('balance', 'alice', 5)],)
Expected Output: [True, (20, [(20, 20)])]
Explanation: A lot is valid only when `timestamp < expiry_time`, so the lot expiring at 5 is already invalid at time 5. The subtraction uses the 30-credit lot expiring at 20, leaving 20.
Input: ([('add', 'u', 5, 4), ('add', 'u', 7, 10), ('subtract', 'u', 10, 4), ('balance', 'u', 4)],)
Expected Output: [False, (7, [(7, 10)])]
Explanation: At time 4, the lot with expiry 4 is expired. Only 7 valid credits remain, so subtracting 10 fails and the ledger stays unchanged.
Input: ([('add', 'bob', 10, 20), ('add', 'alice', 8, 15), ('add', 'bob', 5, 8), ('balance', 'bob', 7), ('subtract', 'bob', 6, 7), ('balance', 'bob', 7), ('balance', 'alice', 7)],)
Expected Output: [(15, [(5, 8), (10, 20)]), True, (9, [(9, 20)]), (8, [(8, 15)])]
Explanation: Bob's balance at time 7 includes both lots, sorted by expiry. Subtracting 6 spends 5 from the lot expiring at 8 and 1 from the lot expiring at 20. Alice's credits are unaffected.
Input: ([('subtract', 'ghost', 0, 3), ('balance', 'ghost', 3), ('subtract', 'ghost', 1, 3)],)
Expected Output: [True, (0, []), False]
Explanation: Subtracting 0 always succeeds, even for a user with no credits. The user's balance is 0, and subtracting 1 then fails.
Input: ([('add', 'u', 5, 10), ('add', 'u', 7, 10), ('subtract', 'u', 6, 1), ('balance', 'u', 1)],)
Expected Output: [True, (6, [(6, 10)])]
Explanation: Both lots have the same expiry, so the earlier-added lot is spent first. The first 5-credit lot is fully consumed, and 1 credit is taken from the second lot, leaving 6.
Hints
- Store each user's credits as separate lots instead of merging everything into one number, because expiry times matter during subtraction.
- For atomic subtraction, first compute whether enough unexpired balance exists before changing any lot.