Debug an Async Payment Refund Function with Retries, Then Make It Idempotent

Quick Overview

Find the defects in a short async Python function that issues a payment refund with up to three attempts and then marks the request done, then explain idempotency and rewrite the function so it is safe to retry and to run more than once. It tests failure-path reasoning, concurrency, error handling and correctness when money moves.

Debug an Async Payment Refund Function with Retries, Then Make It Idempotent

Company: Mercor

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Technical Screen

In a technical screen for an applied AI engineering role, you are handed the Python function below and asked to debug it. It is meant to take a stored refund request, issue the refund through the Stripe payment API with up to three attempts, and then record in the database that the refund is done. ```python async def refund_money(request_id: str): refund = await get_db_record(request_id) if refund.status == "done": return for i in range(3): try: await stripe_api_call( refund.request_id, refund.amount ) break except Error: continue await mark_db_as_done(request_id) ``` Assume that `get_db_record` loads the refund request with the given ID, `stripe_api_call` asks the payment provider to refund `amount` for that request, and `mark_db_as_done` sets the record's status to `"done"`. Separately, the interviewer asked what idempotency is and why we need idempotent operations; this function is the natural place to answer that. ### Clarifying Questions - Can this function run more than once for the same `request_id`, for example when a job is retried or a message is redelivered? - What does `stripe_api_call` return, and which exceptions can it raise? - What statuses can a refund record have besides `"done"`? - When a refund cannot be completed, should the function raise to its caller, record a failure, alert someone, or all three? ### Part 1 — Find the bugs List every defect or risk in this function. For each one, describe a concrete sequence of events that triggers it and what goes wrong for the customer or for the business. ```hint Follow every exit from the loop For each way the loop can finish, ask what the database record says afterward and whether that matches what actually happened at the payment provider. ``` ```hint Think beyond one uninterrupted call The function reads state, calls an external system and then writes state. Consider what happens if that sequence is cut short partway through, or runs more than once. ``` #### What This Part Should Cover - Each defect tied to a specific line and a concrete scenario that triggers it - The consequence of each defect for money movement and for the stored state - A distinction between definite bugs and risks that depend on how the helpers or the provider behave - A ranking of the defects by severity ### Part 2 — Idempotency and a safe rewrite Explain what idempotency is and why operations like this refund need to be idempotent. Then rewrite the function so that it is safe to retry and safe to run more than once for the same `request_id`. State what you need from the database and from the payment provider's API to make your version correct. ```hint Let the provider recognize a repeat Think about what the refund call could carry so that the payment provider can tell a retry of an earlier request apart from a brand-new refund. ``` #### What This Part Should Cover - A precise definition of idempotency, and why retries, timeouts and redelivery make it necessary for payments - A rewrite that keeps the provider's side and the stored state consistent across retries, crashes and overlapping runs - Which failures are retried, how, and which are not - The final stored state for every outcome, including when every attempt fails ### What a Strong Answer Covers - A systematic trace of the code's paths rather than a scan for surface problems - Money correctness first: the customer is never refunded twice, and the system never records a refund that did not happen - A clear view of which system holds the truth about a refund, and how the database and the provider are reconciled - Production concerns: timeouts, backoff, logging, alerting, and how to test the fix ### Follow-up Questions - The provider call times out. Did the refund happen, and how does your design find out? - How would you test that the rewritten function never refunds twice, even with concurrent runs? - Suppose the provider forgets idempotency keys after a day, and a stuck request is retried a week later. What happens, and how do you guard against it? - How would this change if refunds were processed from a message queue with at-least-once delivery?

Overview: Find the defects in a short async Python function that issues a payment refund with up to three attempts and then marks the request done, then explain idempotency and rewrite the function so it is safe to retry and to run more than once. It tests failure-path reasoning, concurrency, error handling and correctness when money moves.

|Home/Software Engineering Fundamentals/Mercor
Mercor logo
Mercor
Sep 3, 2026
mediumSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
0
0

In a technical screen for an applied AI engineering role, you are handed the Python function below and asked to debug it. It is meant to take a stored refund request, issue the refund through the Stripe payment API with up to three attempts, and then record in the database that the refund is done.

async def refund_money(request_id: str):
    refund = await get_db_record(request_id)
    if refund.status == "done":
        return
    for i in range(3):
        try:
            await stripe_api_call(
                refund.request_id,
                refund.amount
            )
            break
        except Error:
            continue
    await mark_db_as_done(request_id)

Assume that get_db_record loads the refund request with the given ID, stripe_api_call asks the payment provider to refund amount for that request, and mark_db_as_done sets the record's status to "done". Separately, the interviewer asked what idempotency is and why we need idempotent operations; this function is the natural place to answer that.

Clarifying Questions Guidance

  • Can this function run more than once for the same request_id , for example when a job is retried or a message is redelivered?
  • What does stripe_api_call return, and which exceptions can it raise?
  • What statuses can a refund record have besides "done" ?
  • When a refund cannot be completed, should the function raise to its caller, record a failure, alert someone, or all three?

Part 1 — Find the bugs

List every defect or risk in this function. For each one, describe a concrete sequence of events that triggers it and what goes wrong for the customer or for the business.

What This Part Should Cover Guidance

  • Each defect tied to a specific line and a concrete scenario that triggers it
  • The consequence of each defect for money movement and for the stored state
  • A distinction between definite bugs and risks that depend on how the helpers or the provider behave
  • A ranking of the defects by severity

Part 2 — Idempotency and a safe rewrite

Explain what idempotency is and why operations like this refund need to be idempotent. Then rewrite the function so that it is safe to retry and safe to run more than once for the same request_id. State what you need from the database and from the payment provider's API to make your version correct.

What This Part Should Cover Guidance

  • A precise definition of idempotency, and why retries, timeouts and redelivery make it necessary for payments
  • A rewrite that keeps the provider's side and the stored state consistent across retries, crashes and overlapping runs
  • Which failures are retried, how, and which are not
  • The final stored state for every outcome, including when every attempt fails

What a Strong Answer Covers Guidance

  • A systematic trace of the code's paths rather than a scan for surface problems
  • Money correctness first: the customer is never refunded twice, and the system never records a refund that did not happen
  • A clear view of which system holds the truth about a refund, and how the database and the provider are reconciled
  • Production concerns: timeouts, backoff, logging, alerting, and how to test the fix

Follow-up Questions Guidance

  • The provider call times out. Did the refund happen, and how does your design find out?
  • How would you test that the rewritten function never refunds twice, even with concurrent runs?
  • Suppose the provider forgets idempotency keys after a day, and a stuck request is retried a week later. What happens, and how do you guard against it?
  • How would this change if refunds were processed from a message queue with at-least-once delivery?
Loading comments...