89. Design GPU Credit Calculator
Design GPU Credit Calculator
Build a
GPU credit calculator for a single account. Credits are added over time, can be partially consumed, and expire after a fixed lifetime.
The calculator must support adding credit grants, spending credits, and querying the remaining balance at any timestamp.
Critical constraint: all operations that
record a ledger event happen at the
current timestamp, and these timestamps are
non-decreasing (monotonically increasing) across such calls.
Specifically:
addCredit(...) records an ADD event at the current timestamp.
useCredit(...) records a USE event at the current timestamp (if successful).
getBalance(timestamp) is a read-only query and may be called for any timestamp (past or future).
Because grants expire, you must treat credits as a
time-ordered ledger (events that you replay up to a timestamp), not as a single evolving “current balance”.
Key Definitions
- Credit Grant: a bucket of credits added by
creditId, with a start time and an expiry time.
- Active Credit: a grant is active at time
t if startTimestamp ≤ t ≤ endTimestamp.
- Expiry Rule: if a grant is added at
timestamp with expiration time units, then:
- It is usable for all timestamps in
[timestamp, timestamp + expiration-1] (inclusive).
- It becomes expired starting at
timestamp + expiration.
- Earliest-Expiry-First Spending: when spending, consume credits from the active grants that expire sooner first (to minimize waste).
If there are multiple such grants then spend from the grant with lexicographically smaller creditId.
- Current Timestamp: an integer time value supplied by the caller. For all calls that record ledger events, the provided timestamp is the current time and is non-decreasing across those calls.
Method Signatures
1) Add credit
boolean addCredit(String creditId, int amount, int timestamp, int expiration)
timestamp is the current timestamp of this event.
creditId uniquely identifies a single credit grant.
amount credits become usable starting at timestamp.
- The grant expires after
expiration time units (see Expiry Rule).
- Return
false if a grant with the same creditId already exists; otherwise store it and return true.
2) Spend credit
boolean useCredit(int timestamp, int amount)
timestamp is the current timestamp of this event.
- Spend
amount credits at the given timestamp.
- Only credits that are active at
timestamp can be spent.
- Spending must follow earliest-expiry-first. If multiple active grants have the same expiry time, break ties by
creditId lexicographically.
- If there is insufficient active credit at
timestamp, return false and do not record this spend.
- If successful, record the spend and return
true.
3) Query balance
Integer getBalance(int timestamp)
- Return the total remaining credits usable at the given
timestamp, after applying all recorded adds and successful spends with time ≤ timestamp.
getBalance does not have to be monotonic and can query any timestamp.
- Return
0 if no balance has been added or all balance has been spent or expired.
Constraints
1 ≤ amount ≤ 10000
0 ≤ timestamp ≤ 10^9
0 ≤ expiration ≤ 10^9
creditId is a non-empty string.
Example 1: Multiple grants (expiry + earliest-expiry-first + lexicographic tie-break) + duplicate add + failed spend + 0 balance
GPUCreditCalculator gpuCredit = new GPUCreditCalculator();
gpuCredit.addCredit(creditId="b", amount=5, timestamp=10, expiration=5) → true
"b" usable in [10, 14], expires starting at 15.
gpuCredit.addCredit(creditId="a", amount=7, timestamp=11, expiration=4) → true
"a" usable in [11, 14], expires starting at 15. (Same expiry as "b"; tie-break by creditId.)
gpuCredit.addCredit(creditId="c", amount=10, timestamp=12, expiration=10) → true
"c" usable in [12, 21], expires starting at 22.
gpuCredit.addCredit(creditId="a", amount=1, timestamp=13, expiration=10) → false
Duplicate creditId "a" is rejected.
gpuCredit.getBalance(timestamp=9) → 0
At time 9, no grants have started yet, so balance is 0 .
gpuCredit.useCredit(timestamp=14, amount=21) → true
At time 14, active credits total 22 ("a" 7, "b" 5, "c" 10). Spend earliest-expiry-first: "a" (lexicographically smaller than "b") then "b" then "c". Consume 7 from "a", 5 from "b", and 9 from "c" → remaining: "c" has 1.
gpuCredit.getBalance(timestamp=14) → 1
Only "c" has 1 remaining at time 14.
gpuCredit.useCredit(timestamp=15, amount=2) → false
At time 15, "a" and "b" are expired; only "c" has 1 active credit, so spending 2 fails and is not recorded.
gpuCredit.getBalance(timestamp=15) → 1
Failed spend at time 15 did not change the ledger; "c" still has 1.
gpuCredit.getBalance(timestamp=21) → 1
At time 21, "c" is still active and still has 1 remaining.
gpuCredit.getBalance(timestamp=22) → 0
At time 22, "c" is expired (usable only through 21), so balance is 0 → 0.
Example 2: Time-travel queries (past/future) + spend affects only timestamps ≥ spend time + expiration=0 edge
GPUCreditCalculator gpuCredit = new GPUCreditCalculator();
gpuCredit.addCredit(creditId="x", amount=10, timestamp=50, expiration=60) → true
"x" usable in [50, 109], expires starting at 110.
gpuCredit.getBalance(timestamp=40) → 0
At time 40, "x" hasn't started yet, so balance is 0 .
gpuCredit.getBalance(timestamp=100) → 10
At time 100, "x" is active and unspent.
gpuCredit.useCredit(timestamp=105, amount=5) → true
At time 105, "x" is active; spend 5, leaving 5.
gpuCredit.getBalance(timestamp=100) → 10
Spend at 105 is after 100, so it does not affect the balance at time 100.
gpuCredit.getBalance(timestamp=106) → 5
At time 106, the spend at 105 applies; remaining is 5.
gpuCredit.addCredit(creditId="zero", amount=7, timestamp=110, expiration=0) → true
Expiration=0 means usable window is empty ([110, 109]), so "zero" is never active.
gpuCredit.getBalance(timestamp=109) → 5
At time 109, "x" is still active and has 5 remaining; the add at time 110 is after 109 so it doesn't apply.
gpuCredit.getBalance(timestamp=110) → 0
At time 110, "x" is expired (starting at 110) and "zero" is never active, so balance is 0 .
gpuCredit.useCredit(timestamp=111, amount=1) → false
At time 111, there is no active credit, so the spend fails and is not recorded.