Design a subscription email scheduler with changes and renewals
Company: Stripe
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates event-driven scheduling, deterministic time-based ordering, stateful account updates for plan changes and renewals, and algorithmic merging of notification streams in the Coding & Algorithms domain.
Part 1: Baseline Subscription Email Scheduling
Constraints
- 0 <= len(user_accounts) <= 100000
- Account names are unique.
- 0 <= duration <= 1000000000
- account_date may be any integer day value.
- send_schedule contains at most 100 negative integer offset keys.
- Negative offset keys are strictly less than 0.
Examples
Input: ({'start': 'Welcome', -15: 'Upcoming expiration', 'end': 'Expired'}, [{'name': 'Alice', 'plan': 'Basic', 'account_date': 0, 'duration': 30}, {'name': 'Bob', 'plan': 'Pro', 'account_date': 10, 'duration': 20}])
Expected Output:
Explanation: Alice and Bob have the same end date, so their relative and end notifications tie by timestamp and are sorted by name.
Input: ({'start': 'Welcome', -10: 'Ten days left', -5: 'Five days left', 'end': 'Expired'}, [{'name': 'Ann', 'plan': 'Basic', 'account_date': 0, 'duration': 10}, {'name': 'Zoe', 'plan': 'Plus', 'account_date': 5, 'duration': 5}])
Expected Output:
Explanation: At time 0, Ann's start event comes before relative-offset events. At time 5, Zoe's start event comes before all relative-offset events.
Input: ({'start': 'Welcome', -1: 'Reminder', 'end': 'Expired'}, [])
Expected Output:
Explanation: No accounts means no notifications.
Input: ({'start': 'Started', 'end': 'Ended'}, [{'name': 'Solo', 'plan': 'Free', 'account_date': 7, 'duration': 0}])
Expected Output:
Explanation: A zero-duration subscription starts and ends at the same timestamp, and start events come before end events.
Hints
- Convert every account notification into a sortable event tuple.
- The sort key should encode both timestamp and the required same-timestamp priority rules.
Part 2: Subscription Scheduling with Plan Changes
Constraints
- 0 <= len(user_accounts) <= 100000
- 0 <= len(changes1) <= 100000
- Account names are unique.
- Every change references an existing account.
- At most one plan change exists for the same account at the same timestamp.
- send_schedule contains at most 100 negative integer offset keys.
- Negative offset keys are strictly less than 0.
Examples
Input: ({'start': 'Welcome', -5: 'Reminder', 'end': 'Expired'}, [{'name': 'Alice', 'plan': 'Basic', 'account_date': 0, 'duration': 10}], [{'name': 'Alice', 'new_plan': 'Pro', 'change_date': 7}])
Expected Output:
Explanation: The reminder is before the plan change, while the end notification is after it.
Input: ({'start': 'Welcome', 'end': 'Expired'}, [{'name': 'Bob', 'plan': 'Basic', 'account_date': 5, 'duration': 5}], [{'name': 'Bob', 'new_plan': 'Gold', 'change_date': 5}])
Expected Output:
Explanation: The change and start happen at the same timestamp, so the change is emitted first and the start uses the new plan.
Input: ({'start': 'Welcome', 'end': 'Expired'}, [{'name': 'Bob', 'plan': 'Basic', 'account_date': 0, 'duration': 10}, {'name': 'Alice', 'plan': 'Free', 'account_date': 0, 'duration': 10}], [{'name': 'Bob', 'new_plan': 'Enterprise', 'change_date': 10}, {'name': 'Alice', 'new_plan': 'Premium', 'change_date': 10}])
Expected Output:
Explanation: Events are sorted by name within the Changed bucket and within the end bucket. End notifications use the updated plans.
Input: ({'start': 'Welcome', -3: 'Reminder', 'end': 'Expired'}, [], [])
Expected Output:
Explanation: No accounts and no changes produce no output.
Hints
- You can still create a single global event list, but change events must be processed before scheduled notifications at the same time.
- Store the current plan for each account separately from the event list so later events see updates.
Part 3: Subscription Scheduling with Plan Changes and Renewals
Constraints
- 0 <= len(user_accounts) <= 100000
- 0 <= len(changes) <= 100000
- Account names are unique.
- Every change or renewal references an existing account.
- At most one plan change and at most one renewal exist for the same account at the same timestamp.
- extension is a positive integer.
- send_schedule contains at most 100 negative integer offset keys.
- Negative offset keys are strictly less than 0.
Examples
Input: ({'start': 'Welcome', -5: 'Reminder', 'end': 'Expired'}, [{'name': 'Alice', 'plan': 'Basic', 'account_date': 0, 'duration': 10}], [{'name': 'Alice', 'extension': 10, 'change_date': 4}])
Expected Output:
Explanation: The renewal at time 4 moves the end date from 10 to 20, so the old reminder at 5 and old end at 10 are canceled.
Input: ({'start': 'Welcome', -2: 'Reminder', 'end': 'Expired'}, [{'name': 'Alice', 'plan': 'Basic', 'account_date': 0, 'duration': 10}], [{'name': 'Alice', 'extension': 5, 'change_date': 8}, {'name': 'Alice', 'new_plan': 'Pro', 'change_date': 8}])
Expected Output:
Explanation: Even though the renewal appears first in the input, Changed is processed before Renewed at the same timestamp. The old reminder at time 8 is canceled and recomputed for the new end date.
Input: ({'start': 'Welcome', -5: 'Reminder', 'end': 'Expired'}, [{'name': 'Bob', 'plan': 'Basic', 'account_date': 0, 'duration': 10}], [{'name': 'Bob', 'extension': 5, 'change_date': 10}])
Expected Output:
Explanation: The original reminder at 5 has already been emitted. At time 10, renewal happens before the old end event, so the old end is canceled. The new end is 15, which creates a new -5 reminder at time 10.
Input: ({'start': 'Welcome', -1: 'Reminder', 'end': 'Expired'}, [{'name': 'Cara', 'plan': 'Free', 'account_date': 1, 'duration': 2}], [{'name': 'Cara', 'new_plan': 'Plus', 'change_date': 2}])
Expected Output:
Explanation: A plan change at the same timestamp as a relative-offset notification is emitted first, so the reminder uses the new plan.
Input: ({'start': 'Welcome', -3: 'Reminder', 'end': 'Expired'}, [], [])
Expected Output:
Explanation: No accounts and no changes produce no output.
Hints
- A min-heap works well because renewals can create newly scheduled future events while the simulation is running.
- Use a per-account version number to invalidate old end-relative events after a renewal.