OpenAI 面试题 #2 – Credits 信用系统设计题 – 一亩三分地 – VO

8次阅读
没有评论

Credits

You’re building a system to track balances for the OpenAI API credit purchasing system.

API users can purchase credit grants, specified by an ID, that are active at a timestamp and that let them use the API. Unused credit grant balances expire at a certain time.

However, there’s a kink in our system: requests can arrive out of order because our system is built on a really unstable network. For example, a request to subtract credits can arrive at the system before the request to add credits, even though the request to add credits has a lower timestamp.

Your task is to implement the Credits class, which should support the following operations:

  • Granting credits, subtracting credits, and getting the credit balance for a user.
  • The ability to handle requests that arrive out of order.

Some guidelines/hints:

  • Do NOT worry about memory or performance concerns. Write simple, working code. No fancy data structures are needed here.
  • All timestamps can be represented as ints for simplicity and are unique (no two actions will happen at the same timestamp).
  • Subtract from grants expiring soonest first.

Skeleton

class Credits:
    def create_grant(
        self,
        timestamp: int,
        grant_id: str,
        amount: int,
        expiration_timestamp: int,
    ) -> None:
        pass

    def subtract(self, timestamp: int, amount: int) -> None:
        pass

    def get_balance(self, timestamp: int) -> int | None:
        pass

Tests

Test case 1 — basic subtraction

def test_basic_subtraction():
    credits = Credits()
    credits.subtract(30, amount=1)
    credits.create_grant(10, grant_id="a", amount=1, expiration_timestamp=100)
    assert credits.get_balance(10) == 1
    assert credits.get_balance(30) == 0
    assert credits.get_balance(20) == 1
# Explanation:
# t=10: +1 (a)
# t=20: 1 (a)
# t=30: 0 (a -1)

Test case 2 — expiration

def test_expiration():
    credits = Credits()
    credits.subtract(30, amount=1)
    credits.create_grant(10, grant_id="a", amount=2, expiration_timestamp=100)
    assert credits.get_balance(10) == 2
    assert credits.get_balance(20) == 2
    assert credits.get_balance(30) == 1
    assert credits.get_balance(100) == 0
# Explanation:
# t=10: +2 (a)
# t=20: 2 (a)
# t=30: 1 (a -1)
# t=100: 0 (the remainder of a expired)

Test case 3 — subtracting from soonest-expiring grants first

def test_expiring_grants_soonest():
    credits = Credits()
    credits.create_grant(10, grant_id="a", amount=3, expiration_timestamp=60)
    assert credits.get_balance(10) == 3
    credits.create_grant(20, grant_id="b", amount=2, expiration_timestamp=40)
    credits.subtract(30, amount=1)
    credits.subtract(50, amount=3)
    assert credits.get_balance(10) == 3
    assert credits.get_balance(20) == 5
    assert credits.get_balance(30) == 4
    assert credits.get_balance(40) == 3
    assert credits.get_balance(50) == 0
# Explanation:
# t=10: 3 (a)
# t=20: 5 (a=3, b=2)
# t=30: 4 (subtract 1 from b since it expires first → b=1, a=3)
# t=40: 3 (b expired)
# t=50: 0 (subtract 3 from a)

Test case 4 — subtract from many grants

def test_many_grants():
    credits = Credits()
    credits.create_grant(10, grant_id="a", amount=3, expiration_timestamp=60)
    credits.create_grant(20, grant_id="b", amount=2, expiration_timestamp=80)
    credits.subtract(30, amount=4)
    assert credits.get_balance(10) == 3
    assert credits.get_balance(20) == 5
    assert credits.get_balance(30) == 1
    assert credits.get_balance(70) == 1
# Explanation:
# t=10: 3 (a)
# t=20: 5 (a=3, b=2)
# t=30: 1 (subtract 3 from a, 1 from b)
# t=70: 1 (a expired; b=1)

Test case 5 — not enough credit

def test_insufficient_credit():
    credits = Credits()
    credits.create_grant(10, grant_id="a", amount=3, expiration_timestamp=60)
    credits.subtract(20, amount=4)
    credits.create_grant(40, grant_id="b", amount=10, expiration_timestamp=60)
    assert credits.get_balance(10) == 3
    assert credits.get_balance(20) is None   # instead of -1
    assert credits.get_balance(50) is None
# Explanation:
# t=10: 3 (a)
# t=20: None (subtract 4 when only 3 available)
# t=50: None (already went negative at t=20; can't recover)

事件重放 最简单:把所有 create_grant/subtract 作为事件按时间排序,重放到查询 / 操作时刻 t

活跃额度=create_time ≤ t < expire_time 的 grant 之和;扣减按 最早过期优先 逐个 grant 消耗。

若在某时刻扣减超出可用额度,该时刻起余额为 None

不要求性能,确保正确性即可。

VOprep 团队长期陪同学员实战各类大厂 OA 与 VO,包括 OpenAI、Google、Amazon、Citadel、SIG 等,提供实时语音助攻、远程陪练与面试节奏提醒,帮助大家在关键时刻不卡壳。
如果你也在准备 Stripe 或类似工程向公司,可以了解一下我们的定制助攻方案——从编程面到系统设计,全程护航上岸。

正文完
 0