Material

Backend practice: APIs and diagnostics

Roadmap stageWeb framework →

These independent assignments use synthetic data. Try them first, then check edge cases and open the hint. Time is an estimate; allow additional time for project tests and documentation.

Choose your practice · Interview mistakes

Scroll the table horizontally →

Assignment Stage Estimate
Review an order from another restaurant Web framework 50 min
Two bookings for the last table Web framework 60 min
Recipes and ingredients in an ORM Web framework 45 min
A menu page grew to 61 queries Web framework 40 min
The kitchen received an order twice Async and background jobs 45 min
The image rebuilds while the API waits for the database Production basics 35 min
A cache must not expose another guest’s booking HTTP and APIs 35 min

Review an order from another restaurant

Review the handler fragment, identify its problems and propose a correction. The restaurant comes from the URL; the server must calculate the amount from its catalog. Retrying after a timeout must not create another order. A dish can be missing or belong to another restaurant. Also explain publication failure after the database write.

@app.post("/restaurants/{restaurant_id}/orders")
async def place_order(restaurant_id: int, body: dict, user=Depends(current_user)):
    dish = await db.fetchrow(
        f"SELECT * FROM dishes WHERE id = {body['dish_id']}"
    )
    await db.execute(
        "INSERT INTO orders(user_id, dish_id, amount) VALUES ($1, $2, $3)",
        user.id, dish["id"], body["amount"]
    )
    await publish({"type": "order_created", "user_id": user.id})
    return {"ok": True}

Check your result

  • Address parameterized SQL, input validation and restaurant ownership separately.
  • Do not trust client-supplied prices or totals.
  • Explain local-write atomicity, request retries and event redelivery.
Hint — after your attempt

Inspect each trust boundary and each step that can fail independently.

Extension: Write an HTTP test for an authenticated user ordering a dish from another restaurant.

Back to this stage

Two bookings for the last table

A restaurant has one free table in a slot. Implement a booking endpoint and batch import. Import is all-or-nothing. Two requests may execute concurrently. A slot’s “closed for booking” status is business state; a database row lock temporarily coordinates transactions. Do not confuse them. Define lock-wait policy, HTTP responses and behavior on retry with the same key.

Check your result

  • Two concurrent clients create exactly one new booking.
  • A mid-batch failure rolls back the entire batch.
  • The same key returns the same result; a changed payload under that key is rejected.
Hint — after your attempt

Availability checking and seat allocation need one concurrency guarantee.

Extension: Compare a conditional UPDATE, a uniqueness constraint and a row lock using two transactions.

Back to this stage

Recipes and ingredients in an ORM

Design SQLAlchemy models Dish, Ingredient and RecipeLine. Each recipe line links a dish to an ingredient and stores a positive gram quantity. An ingredient occurs at most once per dish. Ingredients have an allergen flag. Archiving a dish preserves its recipe; deletion of an ingredient still in use is forbidden. Load a menu and each dish’s allergens without a query per recipe line.

Check your result

  • DDL includes foreign keys, pair uniqueness and a positive-quantity CHECK.
  • Test creation, duplicate ingredients, archiving and forbidden deletion.
  • Measure query count for a 30-dish menu.
Hint — after your attempt

The relationship has its own quantity; model it as an association entity.

Extension: Include dishes without ingredients and explain how JOIN affects those rows.

Back to this stage

A menu page grew to 61 queries

Adding photos and allergens made a menu response run 61 SQL queries for 30 dishes. A separate latest-bookings endpoint slowed as its table grew. You have query counts and p95; a relationship between the issues is not established. Design a diagnostic plan and a minimal experiment for each. After fixing them, report query count, execution plan and p95 under the same load.

Check your result

  • Do not treat an index as a universal fix for N+1.
  • Separate query count, individual query cost and connection wait time.
  • Record inputs, dataset size and success criteria.
Hint — after your attempt

First find the step that adds a query per dish.

Extension: Test the page without photos and without allergens separately.

Back to this stage

The kitchen received an order twice

The order was saved in PostgreSQL, but event publication timed out. The client retried, the publisher retried, and the consumer crashed after creating a kitchen ticket but before acknowledging the message. Design order creation and event handling so retries cannot create another ticket. Distinguish request, order and event identifiers. Broker guarantees must not replace business-effect checks.

Check your result

  • Describe the local order/event transaction, retrying delivery and consumer deduplication.
  • Test crashes before and after ticket creation.
  • Discuss per-order event ordering separately from deduplication.
Hint — after your attempt

Keep the effect and the processed-event marker consistent.

Extension: Handle a cancellation arriving before an older creation event.

Back to this stage

The image rebuilds while the API waits for the database

Changing one application line reruns dependency installation in Docker. Separately, the API often waits for PostgreSQL connections after scaling out. Diagnose the issues separately: propose build-layer ordering and a connection budget. Each replica has a 20-connection pool; there are 6 replicas, and the application’s database budget is 80. Account for background workers and old/new replicas overlapping during rollout.

Check your result

  • 120 potential connections exceed the budget even before workers and rollout overlap.
  • A Python-only change reuses the dependency layer.
  • Measure build time, pool wait time and database load.
Hint — after your attempt

Replica count multiplies each pool’s settings.

Extension: Test rollout at the maximum simultaneous replica count.

Back to this stage

A cache must not expose another guest’s booking

A personal-bookings endpoint requires authentication. After enabling a shared HTTP cache, another guest saw someone else’s list. Define separate cache rules for the public menu and personal bookings. Use menu version as the public ETag basis and handle If-None-Match. For personal data, choose a safe contract first, then justify cache keys and permitted storage if caching is needed at all.

Check your result

  • An unchanged menu returns bodyless 304 for a valid conditional request.
  • Updating the menu changes its ETag.
  • Test two users, anonymous requests and switching users.
Hint — after your attempt

Public and personal resources have different access boundaries.

Extension: Discuss caching errors and restrictions on personal URLs in logs.

Back to this stage