A backend performance challenge inspired by Rinha de Backend. Build a flash sale inventory API that handles massive concurrent traffic on minimal hardware — without crashing, overselling, or losing data.
You are building the backend for a flash sale platform. Products have limited stock and thousands of users will attempt to purchase them simultaneously. Your API must guarantee:
- No overselling — stock must never go negative, even under heavy concurrency.
- Idempotency — retried requests with the same idempotency key must not create duplicate orders or decrement stock twice.
- Atomicity — stock decrement and order creation must succeed or fail together. No partial state.
POST /orders
Request Body:
{
"product_id": 1,
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
"customer_id": "customer_42",
"quantity": 2
}Validation rules:
| Field | Rule |
|---|---|
product_id |
Required. Integer. Must reference an existing product. |
idempotency_key |
Required. UUID v4 format. |
customer_id |
Required. Non-empty string, max 50 chars. |
quantity |
Required. Integer, min 1, max 5. |
Responses:
| Status | Condition |
|---|---|
201 |
Order created successfully. |
200 |
Idempotent replay — order already exists for this key. Return the original order. |
404 |
Product not found. |
409 |
Insufficient stock. |
422 |
Invalid request body (failed validation). |
Success response body (201 or 200):
{
"id": "uuid-here",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
"product_id": 1,
"customer_id": "customer_42",
"quantity": 2,
"unit_price": 4999,
"total_price": 9998,
"status": "CONFIRMED",
"created_at": "2025-01-15T10:30:00Z"
}Critical: If two concurrent requests arrive with different idempotency keys for the last remaining unit, exactly one must succeed and the other must receive
409.
POST /orders/{order_id}/cancel
Request Body: None required.
Responses:
| Status | Condition |
|---|---|
200 |
Order cancelled. Stock restored. |
404 |
Order not found. |
409 |
Order already cancelled (cancel is idempotent-safe, but return 409). |
Success response body (200):
{
"order_id": "gen-uuid-here",
"status": "CANCELLED",
"restored_quantity": 2
}Critical: Cancelling an order must atomically restore stock. A cancelled order cannot be cancelled again (return
409), but concurrent cancel requests for the same order must not restore stock twice.
GET /products/{product_id}
Responses:
| Status | Condition |
|---|---|
200 |
Product found. |
404 |
Product not found. |
Success response body:
{
"id": 1,
"name": "Mechanical Keyboard Ultra",
"price": 4999,
"stock": 100
}
stockmust reflect the real-time accurate count considering all confirmed orders and cancellations.
GET /orders?product_id={product_id}
Returns the last 50 orders for the product, sorted by most recent first.
Responses:
| Status | Condition |
|---|---|
200 |
Product found. |
404 |
Product not found. |
422 |
Missing product_id query parameter. |
Success response body:
{
"product_id": 1,
"orders": [
{
"order_id": "uuid-here",
"customer_id": "customer_42",
"quantity": 2,
"total_price": 9998,
"status": "CONFIRMED",
"created_at": "2025-01-15T10:30:00Z"
}
]
}GET /orders/{order_id}
Returns the order detail.
Responses:
| Status | Condition |
|---|---|
200 |
Order found. |
404 |
Order not found. |
422 |
Invalid order_id path parameter. |
Success response body:
{
"id": "uuid-here",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
"product_id": 1,
"customer_id": "customer_42",
"quantity": 2,
"unit_price": 4999,
"total_price": 9998,
"status": "CONFIRMED",
"created_at": "2025-01-15T10:30:00Z"
}The database must be seeded with these products before the stress test begins:
| ID | Name | Price (cents) | Initial Stock |
|---|---|---|---|
| 1 | Mechanical Keyboard Ultra | 4999 | 100 |
| 2 | Wireless Mouse Pro | 2999 | 50 |
| 3 | USB-C Hub 7-in-1 | 1999 | 200 |
| 4 | 4K Webcam Stream | 8999 | 10 |
| 5 | Noise-Cancel Headphones | 14999 | 30 |
All prices are in cents (integer) to avoid floating-point issues.
Your submission must be a docker-compose.yml containing exactly these services:
┌──────────┐ ┌──────────┐
│ │─────▶│ api-01 │──┐
│ lb │ └──────────┘ │ ┌────────┐
│ :9999 │ ├───▶│ db │
│ │ ┌──────────┐ │ └────────┘
│ │─────▶│ api-02 │──┘
└──────────┘ └──────────┘
| Service | Description |
|---|---|
lb |
Load balancer (e.g., Nginx, HAProxy). Listens on port 9999. Round-robin to api-01 and api-02. |
api-01 |
First API instance. |
api-02 |
Second API instance. |
db |
Database of your choice (PostgreSQL, MySQL, MongoDB, etc.). No in-memory-only stores (Redis, Memcached, etc. as primary DB). |
All services combined must fit within:
| Resource | Limit |
|---|---|
| CPU | 1.5 units total |
| Memory | 550 MB total |
Every service in docker-compose.yml must declare explicit deploy.resources.limits. Example:
services:
api-01:
deploy:
resources:
limits:
cpus: "0.4"
memory: "150MB"You decide how to distribute CPU and memory across services. Tuning this is part of the challenge.
These are the invariants the stress test will validate after the load test completes:
-
Stock integrity — For each product:
current_stock = initial_stock - SUM(confirmed_orders.quantity) + SUM(cancelled_orders.quantity). Stock must never be negative at any point. -
Idempotency — Requests replayed with the same
idempotency_keymust return the sameorder_idand must not decrement stock again. -
No duplicates — Each
idempotency_keymaps to at most one order. -
Order consistency — Every order returned by
GET /orders?product_id={id}must have a matching stock impact. No phantom orders. -
Cancel safety — A cancelled order restores stock exactly once. Concurrent cancels for the same order must not double-restore.
The stress test uses Gatling (or a similar tool like k6) and runs the following scenario:
- Health check polling every 2 seconds on
GET /products/1. - All services must be responding before the load begins.
- High concurrency order placement across all 5 products.
- Mix of new orders and retried idempotency keys (to validate idempotency).
- Intentional overselling attempts — more orders than stock for products with low stock (e.g., product 4 with only 10 units).
- Randomly cancel ~30% of confirmed orders.
- Concurrent cancel requests for the same order (to validate cancel idempotency).
- New orders mixed in (to test stock restoration enables new purchases).
- Fetch all products via
GET /products/{id}and verify stock integrity. - Fetch all orders via
GET /orders?product_id={id}and cross-check. - Verify no duplicate orders exist for the same idempotency key.
| Criteria | Requirement |
|---|---|
| API availability | No crashes. Zero 5xx errors outside of startup. |
| Stock integrity | All 5 products pass the stock equation. |
| Idempotency | Zero duplicate orders. |
| Cancel safety | No double-restored stock. |
| Response time (p99) | Under 500ms for order placement. |
Your repository must contain:
├── docker-compose.yml # Required. Full stack definition.
├── README.md # Brief explanation of your tech choices.
├── sql/init.sql # (if applicable) Database seed script.
└── ... # Your source code.
- Any language, any framework. Go, Rust, Java, C#, Node.js, Python, Elixir — your call.
- Any database. PostgreSQL, MySQL, MongoDB, etc. Must persist to disk (no in-memory-only).
- No external services. Everything must run inside the docker-compose stack.
- Port 9999 is the only entry point. The stress test only hits
localhost:9999. - No cheating with pre-computed results. The API must actually process requests.
| Skill | How |
|---|---|
| Concurrency control | Thousands of simultaneous orders for limited stock. |
| Race conditions | Two requests for the last unit — only one can win. |
| Idempotency | Retried requests must be safe. |
| Atomicity | Stock + order must update together or not at all. |
| Resource efficiency | 1.5 CPU and 550MB RAM for everything. |
| System design | Load balancer config, DB tuning, connection pooling, memory management. |
- Use database-level locking (e.g.,
SELECT ... FOR UPDATE, optimistic locking with version columns, or atomicUPDATE ... WHERE stock >= quantity) to prevent overselling. - Use unique constraints on
idempotency_keyto prevent duplicates at the DB level. - Tune your connection pool — with 550MB total, every byte counts.
- Consider prepared statements and batch inserts for throughput.
- Profile your load balancer config — buffering, keepalive, and upstream timeout settings matter.
- The DB is usually the bottleneck. Give it enough memory for caching.