PracHub
QuestionsPremiumLearningGuidesCheatsheetNEWCoaches

Quick Overview

This question evaluates the ability to aggregate transactional loan records and match repayment transactions to outstanding loans, testing competencies in data aggregation, ordered matching, stateful handling of partial and split payments, and analysis of time and space complexity.

  • medium
  • Affirm
  • Coding & Algorithms
  • Software Engineer

Aggregate loans and match repayments

Company: Affirm

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

You are building a loan servicing tool. You are given two datasets. ### Part 1: Aggregate raw loan records Each raw loan record has the following fields: - `loan_id`: string - `customer_id`: string - `amount`: positive integer - `created_at`: timestamp Multiple records may belong to the same `loan_id`. Write a function that aggregates all records with the same `loan_id` into a single loan summary containing: - `loan_id` - `customer_id` - `created_at` of the earliest record for that loan - `total_amount`: the sum of all amounts for that loan You may assume all records for the same `loan_id` belong to the same `customer_id`. ### Part 2: Match repayment transactions to loans You are then given the aggregated loans from Part 1 and a list of repayment transactions. Each transaction has: - `txn_id`: string - `customer_id`: string - `amount`: positive integer - `timestamp`: timestamp Match transactions to outstanding loans using these rules: 1. A transaction can only be applied to loans for the same `customer_id`. 2. For each customer, loans must be paid in ascending order of `created_at`. 3. A transaction may partially pay a loan. 4. A single transaction may be split across multiple loans. 5. A loan may be paid by multiple transactions. Return: - a list of matches of the form `(txn_id, loan_id, matched_amount)` - the remaining unpaid balance for every loan after all transactions are applied Discuss the time and space complexity of your approach.

Quick Answer: This question evaluates the ability to aggregate transactional loan records and match repayment transactions to outstanding loans, testing competencies in data aggregation, ordered matching, stateful handling of partial and split payments, and analysis of time and space complexity.

Part 1: Aggregate Raw Loan Records

You are given raw loan records. Multiple records may share the same loan_id. Aggregate all records with the same loan_id into one summary. For each loan, keep the same customer_id, the earliest created_at value across its records, and the sum of all amounts as total_amount. Return the summaries sorted by loan_id in ascending order.

Constraints

  • 0 <= len(records) <= 200000
  • 1 <= amount <= 10^9
  • created_at is a comparable integer timestamp
  • All records with the same loan_id belong to the same customer_id

Examples

Input: [{'loan_id': 'L2', 'customer_id': 'C2', 'amount': 40, 'created_at': 2}, {'loan_id': 'L1', 'customer_id': 'C1', 'amount': 100, 'created_at': 5}, {'loan_id': 'L1', 'customer_id': 'C1', 'amount': 50, 'created_at': 3}, {'loan_id': 'L2', 'customer_id': 'C2', 'amount': 60, 'created_at': 4}]

Expected Output: [{'loan_id': 'L1', 'customer_id': 'C1', 'created_at': 3, 'total_amount': 150}, {'loan_id': 'L2', 'customer_id': 'C2', 'created_at': 2, 'total_amount': 100}]

Explanation: L1 combines to 150 with earliest timestamp 3, and L2 combines to 100 with earliest timestamp 2.

Input: [{'loan_id': 'L9', 'customer_id': 'C7', 'amount': 1, 'created_at': 999}]

Expected Output: [{'loan_id': 'L9', 'customer_id': 'C7', 'created_at': 999, 'total_amount': 1}]

Explanation: A single record stays unchanged except that amount becomes total_amount.

Input: []

Expected Output: []

Explanation: No records means no aggregated loans.

Input: [{'loan_id': 'A', 'customer_id': 'X', 'amount': 10, 'created_at': 8}, {'loan_id': 'A', 'customer_id': 'X', 'amount': 15, 'created_at': 8}, {'loan_id': 'A', 'customer_id': 'X', 'amount': 5, 'created_at': 2}, {'loan_id': 'B', 'customer_id': 'X', 'amount': 7, 'created_at': 3}]

Expected Output: [{'loan_id': 'A', 'customer_id': 'X', 'created_at': 2, 'total_amount': 30}, {'loan_id': 'B', 'customer_id': 'X', 'created_at': 3, 'total_amount': 7}]

Explanation: A appears three times and aggregates to total 30 with earliest created_at 2.

Hints

  1. Use a hash map keyed by loan_id to accumulate information as you scan the records once.
  2. For each repeated loan_id, update the running sum and keep the minimum created_at.

Part 2: Match Repayment Transactions to Loans

You are given already-aggregated loans and repayment transactions. Match each transaction to outstanding loans for the same customer. For each customer, loans must be paid in ascending order of created_at, breaking ties by loan_id. Transactions are processed in ascending order of timestamp, breaking ties by txn_id. A transaction may partially pay one loan or be split across multiple loans, and a loan may be paid by multiple transactions. All listed loans are considered outstanding when matching begins.

Constraints

  • 0 <= len(loans), len(transactions) <= 200000
  • Each loan_id is unique in loans
  • Each txn_id is unique in transactions
  • 1 <= total_amount, amount <= 10^9
  • Transactions are applied in ascending timestamp order; if tied, use lexicographic txn_id order
  • Within each customer, loans are paid in ascending created_at order; if tied, use lexicographic loan_id order

Examples

Input: ([{'loan_id': 'L1', 'customer_id': 'C1', 'created_at': 1, 'total_amount': 100}, {'loan_id': 'L2', 'customer_id': 'C1', 'created_at': 3, 'total_amount': 80}], [{'txn_id': 'T1', 'customer_id': 'C1', 'amount': 50, 'timestamp': 10}, {'txn_id': 'T2', 'customer_id': 'C1', 'amount': 100, 'timestamp': 11}])

Expected Output: ([('T1', 'L1', 50), ('T2', 'L1', 50), ('T2', 'L2', 50)], [{'loan_id': 'L1', 'remaining_balance': 0}, {'loan_id': 'L2', 'remaining_balance': 30}])

Explanation: The first transaction partially pays L1. The second finishes L1 and then pays part of L2.

Input: ([{'loan_id': 'L2', 'customer_id': 'C1', 'created_at': 5, 'total_amount': 70}, {'loan_id': 'L1', 'customer_id': 'C1', 'created_at': 2, 'total_amount': 30}, {'loan_id': 'L3', 'customer_id': 'C2', 'created_at': 1, 'total_amount': 40}], [{'txn_id': 'T2', 'customer_id': 'C1', 'amount': 50, 'timestamp': 4}, {'txn_id': 'T1', 'customer_id': 'C2', 'amount': 10, 'timestamp': 3}, {'txn_id': 'T3', 'customer_id': 'C1', 'amount': 20, 'timestamp': 6}, {'txn_id': 'T4', 'customer_id': 'C2', 'amount': 50, 'timestamp': 8}])

Expected Output: ([('T1', 'L3', 10), ('T2', 'L1', 30), ('T2', 'L2', 20), ('T3', 'L2', 20), ('T4', 'L3', 30)], [{'loan_id': 'L1', 'remaining_balance': 0}, {'loan_id': 'L2', 'remaining_balance': 30}, {'loan_id': 'L3', 'remaining_balance': 0}])

Explanation: Transactions are reordered by timestamp, and each customer's loans are paid oldest first.

Input: ([{'loan_id': 'L1', 'customer_id': 'C1', 'created_at': 1, 'total_amount': 25}], [])

Expected Output: ([], [{'loan_id': 'L1', 'remaining_balance': 25}])

Explanation: With no transactions, every loan keeps its full balance.

Input: ([], [{'txn_id': 'T1', 'customer_id': 'C1', 'amount': 100, 'timestamp': 1}])

Expected Output: ([], [])

Explanation: A transaction for a customer with no loans produces no matches.

Input: ([{'loan_id': 'L1', 'customer_id': 'C1', 'created_at': 1, 'total_amount': 50}, {'loan_id': 'L2', 'customer_id': 'C1', 'created_at': 2, 'total_amount': 50}], [{'txn_id': 'T2', 'customer_id': 'C1', 'amount': 30, 'timestamp': 5}, {'txn_id': 'T1', 'customer_id': 'C1', 'amount': 40, 'timestamp': 5}])

Expected Output: ([('T1', 'L1', 40), ('T2', 'L1', 10), ('T2', 'L2', 20)], [{'loan_id': 'L1', 'remaining_balance': 0}, {'loan_id': 'L2', 'remaining_balance': 30}])

Explanation: The timestamps tie, so T1 is processed before T2 because txn_id is the tie-breaker.

Hints

  1. Group loans by customer and sort each customer's loans by the repayment priority order.
  2. Keep a pointer to the first unpaid loan for each customer so you do not rescan fully paid loans.
Last updated: Apr 19, 2026

Loading coding console...

PracHub

Master your tech interviews with 7,500+ 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

  • Determine Redeemable Promotion Offers - Affirm (medium)
  • Compute Available Offers per User - Affirm (easy)
  • Implement a timestamped map - Affirm (medium)
  • Detect fraud events and extract PII - Affirm (medium)
  • Compute Balances and Minimize Settlements - Affirm (hard)