Practical preparation / backend engineering
Backend and API engineering interviews: contracts, concurrency and reliable delivery
Build an interview answer from the request contract to a verified release. Work through a retry-safe inventory reservation, database races, asynchronous delivery and operational evidence, with original exercises and SQL.
- Define
- Contract + invariant
- Challenge
- Retries + concurrent writes
- Prove
- Tests + release signals

Preparation map
Four moves before the interview
- 1
Define the contract
Name the caller, resource, authorization rule and success or failure response.
- 2
Protect the invariant
Choose the transaction boundary, unique identity and concurrency behavior.
- 3
Break the request path
Rehearse lost responses, repeated messages, timeouts and partial failures.
- 4
Ship with evidence
Describe integration checks, deployment compatibility and recovery signals.

01 / FIELD NOTES
Prepare for the ownership in the vacancy
A backend round can be live coding, API design, a database exercise or a production investigation. Use the role's responsibilities to choose depth; a framework name alone does not describe the work.
- Highlight the operations the engineer owns: checkout, identity, integrations, reporting, internal platform or a different domain. Choose one project where you can explain the request, persisted state and customer outcome without revealing confidential data.
- Ask which language, execution environment and format the interview uses. Rehearse in a familiar stack with a real database when available. Have a simpler version ready if the round offers only an editor and no services.
- Bring three small artifacts: an API contract with example errors, a concurrency or failure test, and a release note showing a decision you made. Distinguish your contribution from teammates' work and synthetic practice from production evidence.
- Match seniority through judgment. An early-career candidate can make a narrow endpoint correct and testable; a senior candidate should also reason about migration order, team ownership, service boundaries and the cost of operating the design.
02 / FIELD NOTES
Start with observable API behavior
Write one normal request and the response before drawing infrastructure. The contract should let a client distinguish success, invalid input, insufficient permission, a conflicting state and a temporary failure.
- For POST /reservations, define the SKU, positive integer quantity, tenant identity and application-level request key. Set body and batch limits. An authenticated caller is not automatically authorized to reserve another tenant's stock.
- Use HTTP method semantics deliberately. POST is not inherently idempotent; a repeatable application operation requires an explicit design. Idempotency concerns the intended effect, so status codes or logs can differ across repeated calls without necessarily changing that effect.
- Describe errors as part of the interface. A problem-details response can include a stable type, status and actionable explanation. Keep stack traces, credentials and internal query details out of client responses; log useful diagnostics with a request ID under access control.
- For lists, choose a stable ordering with a unique tie-breaker. Explain what happens when records are added or removed between pages. An opaque cursor should encode the agreed position, not become a substitute for authorization.
- When changing an API, identify old clients and their assumptions. Add an optional field before requiring it, verify compatibility during rollout and agree on deprecation evidence. Version labels cannot repair an undocumented behavioral change.
03 / FIELD NOTES
Put the invariant where concurrent requests meet
A request handler may run on many processes. State the condition that must remain true before choosing a lock, isolation level or retry loop.
- For local stock, require available >= 0. A read of one unit followed by an unguarded update can race. A conditional decrement and appropriate database constraints make the decision where the shared row is changed.
- For repeated requests, use a unique (tenant, request_key) identity and a canonical payload fingerprint. Competing inserts arbitrate ownership. A matching retry waits for or reads the committed result; a different payload using that key receives a conflict.
- Choose which writes commit together: request ownership, stock mutation, reservation, response and outbox intent. Define what happens on constraint failure and rollback. Avoid a committed 'in progress' marker with no expiry or recovery owner.
- Explain the database behavior you rely on. PostgreSQL Read Committed uses statement snapshots and handles a competing update by considering the updated row. More complex multi-row invariants may need a different strategy; serialization failures require retrying the whole transaction.
- Inspect the real access path before adding indexes. Name the query predicate, ordering and expected row count. Compare an execution plan with representative data, including a large tenant. EXPLAIN ANALYZE executes the statement, so use an appropriate test environment for writes.
04 / FIELD NOTES
A timeout does not tell you whether the write happened
Draw the moments before commit, after commit and before the client receives the response. Each boundary can produce a different recovery action.
- If a response is lost after a reservation commits, an automatic new reservation is unsafe. A retry with the original request key should replay the stored result. Document key retention and what the client must do after that retention period ends.
- Keep an external call out of a database transaction unless its consequences are deliberately managed. A local commit cannot atomically include an independent payment provider. Define a pending state, provider idempotency where supported and reconciliation for uncertain outcomes.
- For database state plus event publication, persist an outbox row with the business change. A worker retries publication later. Delivery can repeat after a crash, so the receiving handler needs its own stable event identity and deduplication behavior.
- Give each downstream request a deadline inside the total request budget. Bound retries and use backoff with jitter where appropriate. Retry transient failures only when the operation's semantics allow it; repeated validation errors do not improve with another attempt.
- Discuss overload using the smallest useful boundary: connection limits, queue depth, per-tenant quotas and rejection of work the service cannot finish. Unlimited buffering converts a temporary spike into long delays and expensive recovery.
05 / FIELD NOTES
Test the boundary, then explain the evidence
A handler unit test can establish local branching. It cannot demonstrate how two database connections or a crashed worker interact. Choose checks that exercise the claim you are making.
- Test the contract with valid, invalid and unauthorized inputs, missing resources and stable error shapes. Keep a client-visible expectation for each branch rather than asserting only that an internal helper was called.
- Race two buyers for the last unit using separate connections to the target database. Accept either winner; require exactly one committed reservation and zero remaining units. Synchronize the test so it challenges the shared state instead of running two calls sequentially.
- Inject failure after commit but before response, and after event delivery but before acknowledgment. Verify replayed results and deduplicated side effects. Keep a test for request-key reuse with different content and for cross-tenant key reuse.
- Use realistic load assumptions: arrival rate, burst shape, payload size, slow dependency and a hot key. Report sample count, duration, error rate and tail latency. A short warm-cache benchmark is not evidence for every production workload.
- Show how you would observe a release: completed reservations, conflicts, timeouts, database contention, outbox age and reconciliation discrepancies. Pair a technical metric with a user outcome so a fast service that drops work does not look healthy.
06 / FIELD NOTES
Rehearse a 45-minute design and handoff
This schedule is an original practice format. Your recruiter decides the actual round. Use a timer to make your reasoning and verification visible.
- Minutes 0–7: clarify the caller, resource, tenant rule, failure responses and the invariant. Sketch two competing requests. Name assumptions, such as local reservation rather than payment capture, instead of quietly widening scope.
- Minutes 7–22: write the contract and persisted states. Choose the transaction and request identity. Walk one successful request to commit; then replay the same request and change its payload to show why the fingerprint matters.
- Minutes 22–35: challenge a lost response, worker crash, second buyer and unavailable dependency. Select the recovery policy for each. Implement a narrow core if coding is required; reserve time to execute checks.
- Minutes 35–45: summarize compatibility, release signals and a stop condition. Prepare a question about the team's current bottleneck or ownership boundary. A clear remaining limitation is more useful than claiming the design handles every possible failure.
Worked engineering case
One item left. Two buyers. One lost response.
An illustrative checkout API starts with one unit of SKU book-7. Buyer A sends request key k-41 for one unit; buyer B sends k-42. A's successful response is lost and A retries. The required outcome is one reservation, stock zero and the same reservation ID on A's retry.
Scope and assumptions: This case covers a local reservation, not payment capture. The authenticated tenant, request key and canonical payload fingerprint define a request. Inventory, reservation, stored response and outbox row share one database transaction. All quantities are validated positive integers.
- Step 1
Authorize
Derive tenant identity and validate the request.
- Step 2
Claim request
Unique (tenant, key); compare the stored fingerprint.
- Step 3
Reserve + commit
Guard stock and commit the result and outbox together.
- Step 4
Replay result
A retry returns the saved result without reserving again.
| Request | Decision | Stock after commit | Client result |
|---|---|---|---|
| A · k-41 | Claim key; reserve 1 | 0 | 201 · reservation r-9 |
| B · k-42 | Guard returns no row | 0 | 409 · out of stock |
| A retries k-41 | Match saved fingerprint | 0 | Replay 201 · r-9 |
| A changes k-41 to 2 units | Fingerprint conflict | 0 | 409 · key reused |
-- $1 = positive integer quantity
-- $2 = authenticated tenant; $3 = SKU
UPDATE inventory
SET available = available - $1
WHERE tenant_id = $2
AND sku = $3
AND $1 > 0
AND available >= $1
RETURNING available;One returned row means the decrement happened; zero means this request cannot reserve that stock. The statement prevents an unguarded read-then-write decrement. It does not provide idempotency, tenant authorization or payment handling by itself. Use NOT NULL and CHECK (available >= 0), validate inputs and perform the surrounding transaction above.
Prove the result
- Race k-41 and k-42 on separate database connections. Assert exactly one reservation and stock zero; do not assume which buyer wins.
- Retry k-41 after commit but before its response arrives. Assert the same stored response and no second decrement; reject a changed fingerprint.
- Crash before commit: the request claim and stock decrement both roll back. Crash after commit: the retry reads the committed result.
- Deliver the outbox event twice. Deduplicate by event ID at the consumer; a local transaction does not make an external payment or email exactly once.
Explain where each invariant is enforced. The unique key serializes attempts for one request; the stock guard arbitrates different requests; the outbox preserves the intent to publish after commit. Every boundary still needs a recovery policy.
Original practice bank
Three tasks to work through aloud
These are ApplyDjinn practice prompts, not questions obtained from an employer. Use them to rehearse the reasoning and verification expected for the role.
Exercise 1
Retry-safe reservation
Prompt: One unit remains. Two callers race, and the winning caller loses its HTTP response. Design the endpoint and persisted states so a retry does not reserve twice.
Approach: Specify an authenticated tenant, request key and fingerprint. Use a unique request claim, guarded stock update and one transaction for reservation, stored result and outbox. Walk both commit orders.
Test: Run separate connections, replay after commit, crash before commit, change the payload under the same key and reuse the key in another tenant. Assert state, not the identity of the winner.
Follow-up: How does the behavior change once a payment provider becomes part of the workflow?
Exercise 2
The slow large tenant
Prompt: A list endpoint is fast for most accounts but times out for one large customer. Explain the investigation and a compatible fix.
Approach: Inspect request and query timing, cardinality, query plan and the pagination contract. Check missing predicates, N+1 reads, sort work and cursor stability before choosing an index or cache.
Test: Compare empty, ordinary and large tenants; verify authorization, tied sort values, inserts between pages and cold-cache behavior. Measure database work and tail latency after the change.
Follow-up: What would you do if the new index improves reads but makes bulk imports too expensive?
Exercise 3
Duplicated webhook
Prompt: A receiver stores a webhook, updates an order and crashes before acknowledging the delivery. The sender retries. Make the order update safe.
Approach: Authenticate the sender and validate the event identity. Deduplicate at the receiver's storage boundary and commit the business state with its processed-event record. Define replay and out-of-order behavior.
Test: Deliver the same event concurrently, send a changed payload under the same ID, reverse two event versions and fail after commit. Verify that the order's state matches the chosen ordering rule.
Follow-up: How will you recover an event that was accepted but its downstream email is still pending?
Source check
Primary sources
These primary materials support the published process or technical context. The exercises and preparation framework are original editorial material; confirm your own interview format with the recruiter.
- HTTP semantics and idempotent methods ↗IETF · RFC 9110
- Problem details for HTTP APIs ↗IETF · RFC 9457
- Transaction isolation ↗PostgreSQL
- Constraints ↗PostgreSQL
- INSERT and conflict handling ↗PostgreSQL
- Using EXPLAIN ↗PostgreSQL
- Message delivery semantics ↗Apache Kafka
Continue preparing with evidence
Choose a current role, map its requirements to projects you can defend, then practice the round your recruiter actually scheduled.