Reproduce the defect, make the smallest sound repair and verify it. Open the explanation after your attempt. All data is synthetic.
Choose your practice · Interview mistakes
Practice with an AI agent
Copy this prompt and add the chosen assignment below it:
Act as my backend reviewer. Do not fix the code for me. Ask me to reproduce the defect and explain its cause, then request a minimal patch and a check. After my attempt, assess it against the criteria, point out remaining edge cases and only then show your version. Give one small hint at a time when asked.
Scroll the table horizontally →
| Assignment | Stage | Estimate |
|---|---|---|
| Notices from two guests mixed together | Python basics | 20 min |
| A stream average disappeared | Python engineering | 25 min |
| An empty room got one booking | SQL and PostgreSQL | 20 min |
| The last seat sold twice | Web framework | 30 min |
| A manual from another team is visible by ID | Web framework | 25 min |
Notices from two guests mixed together
Two independent guests share a notice history. Reproduce the defect with two calls, repair the function and verify that an explicitly supplied list is still used.
def add_notice(message, history=[]):
history.append(message)
return history
Check your result
- A second call without a list cannot see the first guest’s notice.
- An explicitly passed list is appended to and returned under the stated contract.
- A test catches repeated-call behavior, not only one call.
Hint — after your attempt
When is the list in the function header created?
Explanation — after your attempt
The default list is created once. Use None as a sentinel and create a list inside each call; when a list is passed, append to that list.
One possible solution:
def add_notice(message, history=None):
if history is None:
history = []
history.append(message)
return history
Extension: Compare a None sentinel with copying the passed list; choose the contract explicitly.
A stream average disappeared
The function works with a list of delays but fails with a one-shot generator. Find the cause and repair it in one pass. Raise ValueError for empty input.
def average_delay(delays):
count = sum(1 for _ in delays)
return sum(delays) / count
Check your result
- A list and a one-shot generator with the same values give the same result.
- Empty input raises an explicit error.
- The repair uses one pass and does not copy the whole stream.
Hint — after your attempt
What remains in the generator after calculating `count`?
Explanation — after your attempt
The first sum exhausts the generator. Accumulate total and count in one loop; raise ValueError if the count is zero.
One possible solution:
def average_delay(delays):
total = 0
count = 0
for delay in delays:
total += delay
count += 1
if count == 0:
raise ValueError('no delays')
return total / count
Extension: Accept a very long stream and measure extra memory.
An empty room got one booking
In rooms, room 7 exists but has no bookings. The query reports one booking. Fix the counter without losing rooms from the report.
SELECT r.id, COUNT(*) AS bookings
FROM rooms AS r
LEFT JOIN bookings AS b ON b.room_id = r.id
GROUP BY r.id;
Check your result
- A room without bookings reports 0; a room with two reports 2.
- Explain
COUNT(*)versusCOUNT(b.id)after aLEFT JOIN. - Keep rooms with no related rows in the result.
Hint — after your attempt
How many rows does `LEFT JOIN` produce for a room without bookings?
Explanation — after your attempt
LEFT JOIN retains one room row with NULL on the right. COUNT(*) counts that row; COUNT(b.id) counts only existing bookings.
One possible solution:
SELECT r.id, COUNT(b.id) AS bookings
FROM rooms AS r
LEFT JOIN bookings AS b ON b.room_id = r.id
GROUP BY r.id;
Extension: Add a booking status and place its filter without losing empty rooms.
The last seat sold twice
Two requests read available = 1 at the same time, then both sell the seat. Find the race and repair the operation so exactly one succeeds. Assume db.fetchval and db.execute each run a separate SQL command.
async def book(seat_id, db):
available = await db.fetchval("SELECT available FROM seats WHERE id = $1", seat_id)
if available is None or available == 0:
return False
await db.execute("UPDATE seats SET available = available - 1 WHERE id = $1", seat_id)
return True
Check your result
- For two concurrent calls and one seat, exactly one succeeds.
- The available count cannot become negative.
- A missing
seat_idreturnsFalse; explain how update success is determined.
Hint — after your attempt
Make the availability check part of the `UPDATE` itself.
Explanation — after your attempt
Separate read and update create a race. Use one UPDATE ... WHERE id = $1 AND available > 0 RETURNING available; a returned row means success.
One possible solution:
async def book(seat_id, db):
remaining = await db.fetchval(
'UPDATE seats SET available = available - 1 '
'WHERE id = $1 AND available > 0 '
'RETURNING available',
seat_id,
)
return remaining is not None
Extension: Add cancellation and ensure it cannot increase the count twice.
A manual from another team is visible by ID
The user is authenticated, but can read another team’s document by knowing its ID. Repair the lookup and handle a missing document without disclosing another team’s data.
async def load_manual(manual_id, user, db):
row = await db.fetchrow(
"SELECT id, team_id, title FROM manuals WHERE id = $1", manual_id
)
if row is None:
return None
return dict(row)
Check your result
- A user can read a document from their own team.
- Foreign and missing documents reveal no data and receive the same response.
- Enforce authorization in the server query rather than a client-side filter.
Hint — after your attempt
How can the user’s `team_id` become part of the lookup?
Explanation — after your attempt
Filtering only by id does not restrict ownership. Add AND team_id = $2 using trusted user.team_id; handle foreign and missing rows identically.
One possible solution:
async def load_manual(manual_id, user, db):
row = await db.fetchrow(
'SELECT id, team_id, title FROM manuals '
'WHERE id = $1 AND team_id = $2',
manual_id,
user.team_id,
)
return None if row is None else dict(row)
Extension: Change a user’s team and verify old access.