PracHub
QuestionsPremiumCoachesLearningGuidesInterview Prep

Quick Overview

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.

  • medium
  • Stripe
  • Coding & Algorithms
  • Software Engineer

Implement Billing Coding Exercises

Company: Stripe

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Onsite

You are interviewing for a software engineering role at a payments company. The coding rounds focused on practical billing workflows. Implement the following tasks as production-quality code: 1. **Invoice and payment reconciliation:** Given invoice records and payment records, match them according to business rules and report which items reconcile successfully and which remain unmatched. Timestamps are provided as normalized datetime strings, so you should handle time comparison carefully and efficiently. 2. **Subscription-based email scheduling:** Given a user's subscription start time and a notification schedule, determine which email notifications should be sent and at what times. 3. **API-based reconciliation integration:** Build a small integration that calls an external reconciliation API. The request must include an authorization header, and the invoice file content must be sent in the request body. Parse the response and return the reconciliation result. Discuss edge cases such as invalid datetime strings, duplicate records, retries, and malformed API responses.

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

Given invoice records and payment records, reconcile them under deterministic billing rules. Each record is a dictionary with keys 'id', 'amount', and 'timestamp' in the normalized format 'YYYY-MM-DD HH:MM:SS'. A valid invoice can match a valid payment only if the amounts are equal, the payment time is not earlier than the invoice time, and the payment time is at most max_delay_minutes after the invoice time. Process invoices in chronological order, breaking ties by id, and assign each invoice the earliest unused eligible payment with the same amount. Records with invalid timestamps, non-positive amounts, or duplicate ids within the same input list are invalid and must be excluded. Return successful matches plus unmatched and invalid ids.

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

  1. Group payments by amount so you never compare records with different amounts.
  2. 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

You are given a subscription start time, a current time, and a notification schedule. Each schedule rule is a 4-tuple: (name, first_offset_minutes, repeat_minutes, occurrences). The first email for that rule is sent at start_time + first_offset_minutes. If repeat_minutes is 0, the rule sends only one email regardless of occurrences. Otherwise, it repeats every repeat_minutes until it has sent at most occurrences emails total. Return every notification whose send time is less than or equal to current_time. Ignore invalid rules and ignore exact duplicate valid rules after the first one. If start_time or current_time is invalid, or current_time is earlier than start_time, return an empty list.

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

  1. You can compute how many repeated sends are due with arithmetic instead of simulating minute by minute.
  2. Store generated events with datetime objects first, then sort once at the end.

Part 3: API-Based Reconciliation Integration

Implement the client-side logic for a reconciliation API integration, but use simulated responses instead of real network calls. The function receives an auth token, the invoice file content to send in the request body, a list of API responses that would be returned on successive attempts, and max_retries. Conceptually, each request must include the header 'Authorization: Bearer <token>' and the raw invoice content in the body. Retry on status codes 429, 500, 502, 503, and 504. Stop immediately on non-retryable 4xx responses. On a 200 response, parse the JSON body and return {'status': str, 'matched': int, 'unmatched': int}. If the token is missing, the body is empty, retries are exhausted, or the response is malformed, return an error dictionary.

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

  1. Separate the logic into three cases: success, retryable failure, and terminal failure.
  2. After parsing JSON, validate field types too; a key being present is not enough.
Last updated: May 31, 2026

Loading coding console...

PracHub

Master your tech interviews with 8,000+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • Assign Reviewers from Changed Files - Stripe (medium)
  • Generate Account Email Notifications - Stripe (medium)
  • Calculate Transaction Fees - Stripe (medium)
  • Build an Account Transfer Ledger - Stripe (medium)
  • Implement Validation and String Compression - Stripe (hard)