Quick Overview

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.

Implement an expiring GPU credits ledger

Company: OpenAI

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

Implement an expiring GPU credits ledger for multiple users with three operations: 1) add_credit(user_id, amount, expiry_time): add a lot of credits that expires at expiry_time. 2) subtract(user_id, amount, timestamp): atomically deduct amount at timestamp by consuming unexpired lots in order of earliest expiry first; lots with expiry_time <= timestamp are expired and cannot be used; if total unexpired balance < amount, the operation must fail and make no changes. 3) get_balance(user_id, timestamp): return the total unexpired balance at timestamp and a breakdown by remaining lots (amount, expiry_time). Assumptions: time is integer seconds; amounts are non-negative integers; partial consumption of a lot is allowed; exact expiry boundary uses the rule "valid iff timestamp < expiry_time". Tasks: implement the three functions; include tests for (a) single lot add and spend, (b) spending across multiple lots with different expiries, (c) attempts to spend after a lot expires, (d) insufficient-balance failure leaving state unchanged, (e) edge case at expiry boundary. Follow up: analyze time/space complexity; discuss how to optimize for many operations (e.g., data structures to achieve O(log n) per update) and how to persist/restore state.

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.

Implement an **expiring GPU credits ledger** that processes a stream of operations for multiple users and reports the results of every query. ## What to implement Implement: ```python def solution(operations): ... ``` `operations` is a list of operation tuples, processed **in the given order**. Each credit a user adds creates a **separate lot** with its own expiry time. A lot is **valid (unexpired) at a timestamp `t` if and only if `t < expiry_time`** (so a lot with `expiry_time <= t` is expired at `t`). Return a list containing the result of every **non-`add`** operation, in the order the operations were processed. `add` operations produce no output. ## Operations Each tuple's first element is its type: - **`('add', user_id, amount, expiry_time)`** — Add a new lot of `amount` credits to `user_id` that expires at `expiry_time`. Produces no result. (An `add` with `amount <= 0` adds nothing.) - **`('subtract', user_id, amount, timestamp)`** — Atomically deduct `amount` credits from `user_id` using only lots that are unexpired at `timestamp`, spending from the lot with the **earliest `expiry_time` first**. **Partial consumption of a lot is allowed** (a lot may be left with a reduced amount). Returns **`True`** on success or **`False`** on failure: - If the total of `user_id`'s unexpired credits is less than `amount`, the operation **fails**: return `False` and leave the ledger **unchanged** (no lots are modified). - Otherwise it succeeds: deduct exactly `amount` earliest-expiry-first and return `True`. - A subtract of `amount == 0` always **succeeds** (returns `True`) and changes nothing, even for a user with no credits. - **`('balance', user_id, timestamp)`** — Report `user_id`'s credits that are unexpired at `timestamp`. Returns a tuple `(total, breakdown)` where: - `total` is the sum of all unexpired lot amounts. - `breakdown` is the list of remaining unexpired lots as `(amount, expiry_time)` pairs, **sorted by `expiry_time`** (ascending). A user with no unexpired credits has balance `(0, [])`. ## Notes - Expired lots never count toward a balance, can never be spent, and should be treated as gone once their `expiry_time` has been reached at the timestamp of a query. - Users are independent; an operation on one `user_id` never affects another. ## 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`. - The `timestamp` values of the **non-`add` operations** (`subtract` and `balance`) are **non-decreasing** in processing order (time only moves forward across queries). `add` operations carry no timestamp and may specify any `expiry_time`. ## Follow-up Analyze the time and space complexity of your approach. Discuss how to optimize toward **O(log n)** updates and queries for heavy workloads, and describe how you would persist and restore the ledger state.

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.

Hints

  1. Store each user's credits as separate lots instead of merging everything into one number, because expiry times matter during subtraction.
  2. For atomic subtraction, first compute whether enough unexpired balance exists before changing any lot.

Loading coding console...