Implement Billing Coding Exercises
Company: Stripe
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates skills in billing and payment reconciliation, datetime normalization and comparison, subscription-based scheduling, API integration with authentication, and robust error handling and response parsing for production-quality code.
Part 1: Invoice and Payment Reconciliation
Constraints
- 0 <= len(invoices), len(payments) <= 20000
- Valid timestamps use the format 'YYYY-MM-DD HH:MM:SS'
- 1 <= amount <= 10^9 for valid records
- 0 <= max_delay_minutes <= 10^6
Examples
Input: ([{'id': 'i1', 'amount': 100, 'timestamp': '2024-01-01 10:00:00'}, {'id': 'i2', 'amount': 50, 'timestamp': '2024-01-01 10:05:00'}], [{'id': 'p1', 'amount': 100, 'timestamp': '2024-01-01 10:03:00'}, {'id': 'p2', 'amount': 50, 'timestamp': '2024-01-01 10:06:00'}], 10)
Expected Output: {'matches': [['i1', 'p1'], ['i2', 'p2']], 'unmatched_invoices': [], 'unmatched_payments': [], 'invalid_invoices': [], 'invalid_payments': []}
Explanation: Both invoices find the earliest eligible payment with the same amount inside the allowed window.
Input: ([{'id': 'i1', 'amount': 100, 'timestamp': '2024-01-01 10:00:00'}, {'id': 'i2', 'amount': 100, 'timestamp': 'not-a-date'}, {'id': 'i1', 'amount': 100, 'timestamp': '2024-01-01 10:05:00'}], [{'id': 'p1', 'amount': 100, 'timestamp': '2024-01-01 09:59:00'}, {'id': 'p2', 'amount': 100, 'timestamp': '2024-01-01 10:07:00'}], 10)
Expected Output: {'matches': [['i1', 'p2']], 'unmatched_invoices': [], 'unmatched_payments': ['p1'], 'invalid_invoices': ['i2', 'i1'], 'invalid_payments': []}
Explanation: One invoice has an invalid timestamp and the later repeated id is invalid. Payment p1 is too early, so only p2 can reconcile.
Input: ([{'id': 'i1', 'amount': 100, 'timestamp': '2024-01-01 10:00:00'}, {'id': 'i2', 'amount': 100, 'timestamp': '2024-01-01 10:01:00'}], [{'id': 'p1', 'amount': 100, 'timestamp': '2024-01-01 10:20:00'}, {'id': 'p2', 'amount': 100, 'timestamp': '2024-01-01 10:02:00'}], 1)
Expected Output: {'matches': [['i2', 'p2']], 'unmatched_invoices': ['i1'], 'unmatched_payments': ['p1'], 'invalid_invoices': [], 'invalid_payments': []}
Explanation: Invoice i1 cannot use p2 because it is outside the 1-minute window, but i2 can still match p2 later.
Input: ([], [], 5)
Expected Output: {'matches': [], 'unmatched_invoices': [], 'unmatched_payments': [], 'invalid_invoices': [], 'invalid_payments': []}
Explanation: Empty inputs should produce empty outputs.
Hints
- Group payments by amount so you never compare records with different amounts.
- When invoices are processed in increasing time order, any payment earlier than the current invoice can never match any later invoice.
Part 2: Subscription-Based Email Scheduling
Constraints
- 0 <= len(schedule) <= 10000
- 0 <= first_offset_minutes, repeat_minutes <= 10^6 for valid rules
- 1 <= occurrences <= 10^5 for valid rules
- The total number of due notifications returned will not exceed 100000
Examples
Input: ('2024-01-01 09:00:00', '2024-01-01 11:30:00', [('welcome', 0, 0, 1), ('trial_tip', 60, 30, 3), ('late', 200, 0, 1)])
Expected Output: [['welcome', '2024-01-01 09:00:00'], ['trial_tip', '2024-01-01 10:00:00'], ['trial_tip', '2024-01-01 10:30:00'], ['trial_tip', '2024-01-01 11:00:00']]
Explanation: The welcome email is immediate, the trial_tip rule produces three due emails, and the late email is not due yet.
Input: ('2024-01-01 09:00:00', '2024-01-01 09:10:00', [('welcome', 0, 0, 3), ('welcome', 0, 0, 3), ('', 5, 0, 1), ('bad', -1, 0, 1)])
Expected Output: [['welcome', '2024-01-01 09:00:00']]
Explanation: The exact duplicate rule is ignored, repeat_minutes = 0 sends only once, and the invalid rules are skipped.
Input: ('2024-01-02 09:00:00', '2024-01-01 09:00:00', [('welcome', 0, 0, 1)])
Expected Output: []
Explanation: If the current time is before the subscription start, no notifications should be sent.
Input: ('2024-01-01 09:00:00', '2024-01-01 10:45:00', [('nudge', 15, 15, 10)])
Expected Output: [['nudge', '2024-01-01 09:15:00'], ['nudge', '2024-01-01 09:30:00'], ['nudge', '2024-01-01 09:45:00'], ['nudge', '2024-01-01 10:00:00'], ['nudge', '2024-01-01 10:15:00'], ['nudge', '2024-01-01 10:30:00'], ['nudge', '2024-01-01 10:45:00']]
Explanation: Seven notifications are due from the repeating rule by 10:45.
Input: ('bad-date', '2024-01-01 10:00:00', [('welcome', 0, 0, 1)])
Expected Output: []
Explanation: Invalid datetime input should produce an empty result.
Hints
- You can compute how many repeated sends are due with arithmetic instead of simulating minute by minute.
- Store generated events with datetime objects first, then sort once at the end.
Part 3: API-Based Reconciliation Integration
Constraints
- 0 <= len(responses) <= 100
- 0 <= max_retries <= 10
- Each simulated response should contain integer 'status_code' and string 'body'
- Successful response bodies are JSON objects with keys 'status', 'matched', and 'unmatched'
Examples
Input: ('token123', 'INV001,100\nINV002,50', [{'status_code': 200, 'body': '{"status": "ok", "matched": 2, "unmatched": 0}'}], 0)
Expected Output: {'status': 'ok', 'matched': 2, 'unmatched': 0}
Explanation: A valid 200 response is parsed and returned immediately.
Input: ('token123', 'INV001,100', [{'status_code': 500, 'body': ''}, {'status_code': 429, 'body': ''}, {'status_code': 200, 'body': '{"status": "ok", "matched": 1, "unmatched": 0}'}], 2)
Expected Output: {'status': 'ok', 'matched': 1, 'unmatched': 0}
Explanation: The first two responses are retryable, and the third succeeds within the allowed retry budget.
Input: ('token123', 'INV001,100', [{'status_code': 401, 'body': 'unauthorized'}], 3)
Expected Output: {'error': 'client_error', 'status_code': 401}
Explanation: A non-retryable 4xx response should stop the integration immediately.
Input: ('token123', 'INV001,100', [{'status_code': 200, 'body': 'not-json'}], 1)
Expected Output: {'error': 'malformed_response'}
Explanation: A 200 response with a malformed body must be rejected.
Input: ('', 'INV001,100', [{'status_code': 200, 'body': '{"status": "ok", "matched": 1, "unmatched": 0}'}], 0)
Expected Output: {'error': 'missing_token'}
Explanation: A missing authorization token is an input validation error.
Hints
- Separate the logic into three cases: success, retryable failure, and terminal failure.
- After parsing JSON, validate field types too; a key being present is not enough.