# Low Level Design: Returns Processing System

Source: https://www.techinterview.org/post/3233470927/lld-returns-processing/
Updated: 2026-06-30 · techinterview.org

## Returns Processing System: Low Level Design

### Core Data Model

Two primary tables capture return requests and the individual items within them:


```
-- Returns table
CREATE TABLE returns (
    id            BIGSERIAL PRIMARY KEY,
    order_id      BIGINT NOT NULL,
    user_id       BIGINT NOT NULL,
    status        VARCHAR(20) NOT NULL DEFAULT 'requested',
                  -- requested | approved | label_sent | received | inspecting | resolved
    reason_code   VARCHAR(30) NOT NULL,
                  -- wrong_item | defective | not_as_described | changed_mind | damaged_in_transit | ...
    resolution    VARCHAR(20),
                  -- refund | exchange | store_credit | rejected
    requested_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Return items table
CREATE TABLE return_items (
    id               BIGSERIAL PRIMARY KEY,
    return_id        BIGINT NOT NULL REFERENCES returns(id),
    order_item_id    BIGINT NOT NULL,
    quantity         INT NOT NULL,
    condition        VARCHAR(20),  -- new | like_new | damaged
    inspection_notes TEXT
);
```


### Return Request Flow


```
Customer initiates return
  → Eligibility check:
      - Item purchased within 30 days?
      - Item not in exclusion list (digital goods, perishables)?
      - Quantity requested ≤ quantity ordered?
  → Auto-approve if: value < $150 AND reason_code in (wrong_item, defective, damaged_in_transit)
  → Manual review queue if: value ≥ $150 OR reason_code = changed_mind
  → Approved → generate prepaid return shipping label
  → Email label PDF to customer, update status = label_sent
```


### Return Label Generation

On approval, the system calls the carrier API (same carrier used for outbound where possible) to generate a prepaid return label:


```
CarrierAPI.createReturnLabel({
    from: customer_address,
    to:   warehouse_address,
    service: 'ground',
    reference: return_id
})
→ Store label_url, tracking_number on returns record
→ Email label to customer
→ Update status = 'label_sent'
```


### Warehouse Inspection Workflow

When the return arrives at the warehouse:

    - Warehouse scans return tracking number → system marks `status = received`.

    - Inspector opens return in WMS UI → grades each item condition: `new` / `like_new` / `damaged`.

    - Inspector adds notes and submits → `status = inspecting` → system applies resolution policy rules.

### Resolution Policy Rules


```
Policy engine evaluates per return_item:

condition = 'new'       → restock + refund approved
condition = 'like_new'  → restock + refund approved (if within policy window)
condition = 'damaged'   → route to liquidation/disposal
                          refund approved if damage caused by carrier/defect
                          refund rejected if customer-caused damage

reason_code = 'changed_mind' + condition = 'damaged' → resolution = rejected

Final resolution set on returns.resolution:
  refund | exchange | store_credit | rejected
```


### Refund Processing


```
IF resolution = 'refund':
    PaymentAPI.refund(order.payment_intent_id, refund_amount_cents)
    → Update order status = 'refunded' (or partially_refunded)
    → Write order event log entry
    → Send refund confirmation email

IF resolution = 'exchange':
    OrderService.createOrder(user_id, exchange_items, payment_total=0)
    → New order created with $0 charge
    → Send exchange confirmation email with new order number

IF resolution = 'store_credit':
    WalletService.credit(user_id, amount_cents)
    → Send store credit notification email
```


### Restocking Logic


```
IF return_item.condition IN ('new', 'like_new'):
    InventoryService.incrementStock(product_id, warehouse_id, quantity)
    InventoryService.releaseReservation(order_item_id)
    -- Item is sellable again

IF return_item.condition = 'damaged':
    LiquidationService.createRecord(return_item_id)
    -- Item routed to liquidation partner or disposed
```


### Fraud Detection

A background job checks return patterns on every new return request:


```
SELECT COUNT(*) FROM returns
WHERE user_id = ? AND requested_at > NOW() - INTERVAL '90 days'

IF count > 3:
    Flag return for manual review
    Add user_id to returns_review_queue with reason = 'high_return_rate'
    Optionally: restrict auto-approve for this user_id
```


Fraud signals also include: same item returned multiple times, returns clustered around sale events, mismatch between returned item weight and expected weight (carrier scan data).

### Analytics & Merchandising Insights


```
-- Return rate by product
SELECT product_id,
       COUNT(ri.id)::FLOAT / SUM(oi.quantity) AS return_rate
FROM return_items ri
JOIN order_items oi ON ri.order_item_id = oi.id
GROUP BY product_id
ORDER BY return_rate DESC;

-- Return rate by reason code
SELECT reason_code, COUNT(*) AS returns
FROM returns
WHERE requested_at > NOW() - INTERVAL '30 days'
GROUP BY reason_code
ORDER BY returns DESC;
```


High return rates on a product surface in the merchandising dashboard as a signal to review product listings, sizing guides, or supplier quality.

### Key API Endpoints


```
POST /returns                     -- customer initiates return
GET  /returns/{id}               -- get return status + items
POST /returns/{id}/approve       -- manual approval (agent)
POST /returns/{id}/inspect       -- warehouse submits inspection grades
GET  /returns?user_id=&status=   -- list returns (admin)
GET  /returns/analytics          -- return rate reports
```


### Key Design Decisions

    - Eligibility and policy rules are externalized to configuration so merchandising can change return windows without a code deploy.

    - Resolution policy is deterministic and auditable — every decision is logged with the rule that triggered it.

    - Refund and exchange flows reuse existing PaymentAPI and OrderService to avoid duplicated logic.

    - Fraud threshold (3 returns / 90 days) is configurable per user segment (e.g., higher threshold for loyalty members).

    - Append-only return event log mirrors the order event pattern for consistent auditability.

See also: [Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering](https://www.techinterview.org/post/3233460275/scale-ai-interview-guide-2026-data-infrastructure-rlhf-pipelines-and-ml-engineering/)

See also: [Shopify Interview Guide](https://www.techinterview.org/post/3233459924/shopify-interview/)

See also: [Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems](https://www.techinterview.org/post/3233460268/stripe-interview-guide-2026-process-bug-bash-round-and-payment-systems/)
