← 返回博客列表
Stripe

Stripe 面試真題解析:Sending Subscription Notifications

2025-12-29

這道題是 Stripe 訂閱系統(Billing)團隊的經典面試題。它考察的是事件調度(Scheduling)時間軸排序以及狀態變更的傳播

題目描述

Stripe 需要給訂閱用戶發送通知郵件。每個用戶都有一個訂閱計劃(Plan),且不同的時間節點需要發送不同的郵件。

Part 1: 靜態調度 (Static Schedule)

給定一個發送計劃模板(Schedule Template)和一組用戶訂閱資訊,請輸出所有需要發送的郵件日誌,按時間排序。

發送計劃模板:

用戶數據:

預期輸出: 你需要計算出每個用戶的具體發送日期(Day 0, Day 15, Day 30),將所有用戶的郵件事件合併,並按時間戳排序輸出。

Part 2: 動態變更 (Dynamic Changes)

用戶可能會在訂閱中途更改 Plan(例如從 Silver 升級到 Gold)。 新規劃:

  1. 如果用戶更改了 Plan,系統需要立即發送一封 "Plan Changed" 郵件。
  2. 後續的所有定時郵件(如 "Expired")都需要在主題中反映最新的 Plan 名稱。

輸入增加: PlanChanges = [{user: "Alice", time: 5, new_plan: "Gold"}]

難點: 你需要動態更新 Alice 在 Day 15 和 Day 30 的待發送郵件內容。

oavoservice 解題思路分析

這道題本質上是一個優先隊列(Priority Queue)事件合併排序問題。

1. 資料結構設計

我們需要一個物件來表示「待發送郵件」:

class EmailEvent:
    time: int
    user_id: str
    template_type: str # e.g., WELCOME, EXPIRY
    original_plan: str
    
    # 用於排序
    def __lt__(self, other):
        return self.time < other.time

2. 處理流程

  1. 初始化:遍歷所有用戶,根據模板生成初始的 EmailEvents 列表。
  2. 合併變更:遍歷 PlanChanges,生成 "Plan Changed" 事件加入列表。
  3. 狀態回溯(關鍵)
    • 最簡單的做法是:在生成輸出時,即時查詢該用戶在 event.time 時刻的有效 Plan。
    • 或者:維護一個 User -> CurrentPlan 的映射。按時間順序處理所有事件(包括郵件發送事件和 Plan 變更事件)。

3. 演算法選擇

程式碼片段 (Python)

def generate_notifications(users, schedule, changes):
    events = []
    
    # 1. 生成基礎郵件事件
    for u in users:
        start = u['start']
        end = start + u['duration']
        # Add Welcome
        events.append({"time": start, "type": "EMAIL", "msg": "Welcome", "user": u})
        # Add Expiry
        events.append({"time": end, "type": "EMAIL", "msg": "Expired", "user": u})
        
    # 2. 生成變更事件
    for c in changes:
        events.append({"time": c['time'], "type": "CHANGE", "new_plan": c['new_plan'], "user_name": c['name']})
        
    # 3. 排序
    events.sort(key=lambda x: x['time'])
    
    # 4. 模擬時間軸
    user_plans = {u['name']: u['plan'] for u in users}
    
    for e in events:
        if e['type'] == "CHANGE":
            user_plans[e['user_name']] = e['new_plan']
            print(f"{e['time']}: Plan Changed for {e['user_name']} to {e['new_plan']}")
        else:
            current_plan = user_plans[e['user']['name']]
            print(f"{e['time']}: {e['msg']} for {e['user']['name']} ({current_plan})")

想知道如何處理更複雜的調度?

如果發送時間是「每月的第某個週一」怎麼辦?如果時區不同怎麼辦? oavoservice 的面試輔助服務會帶你深入探討這些 System Design 級別的 Follow-up,確保你在面試中不僅能寫出程式碼,還能展現架構能力。

聯繫我們