Rapid Retry Detection
A medium SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- medium
- Seniority
- L5
- Asked in
- Vanguard
Problem
List every recovered retry in api_calls: a later call from the same user to the same endpoint, made within 5 minutes of a server-side failure (a 5xx status), that came back with no error message. The on-call engineer wants each pair as the user_id, the endpoint, the failed call's time and the retry's time.
Detect retried API calls within 5 minutes of failure.
Tables
api_calls
Columns: call_id INTEGER, endpoint TEXT, method TEXT, status INTEGER, latency REAL, user_id INTEGER, call_time TEXT, err_msg TEXT
| call_id | endpoint | method | status | latency | user_id | call_time | err_msg |
|---|---|---|---|---|---|---|---|
| 8079 | /api/v1/orders | POST | 200 | 4.2 | 100 | 2026-02-02 01:07:00 | NULL |
| 8158 | /api/v2/products | PUT | 200 | 7.9 | 197 | 2026-03-03 02:14:00 | NULL |
| 8237 | /api/v1/search | DELETE | 201 | 11.6 | 294 | 2026-04-04 03:21:00 | NULL |
| 8316 | /api/v1/auth/login | PATCH | 204 | 15.3 | 391 | 2026-05-05 04:28:00 | NULL |
| 8395 | /api/v1/auth/logout | get | 301 | 19 | NULL | 2026-06-06 05:35:00 | NULL |
| 8474 | /api/v2/analytics | post | 400 | 22.7 | 585 | 2026-07-07 06:42:00 | Not found |
| 8553 | /api/v1/payments | GET | 401 | 26.4 | 682 | 2026-08-08 07:49:00 | Unauthorized |
| 8632 | /api/v1/users/ | POST | 403 | 30.1 | 779 | 2026-09-09 08:56:00 | Rate limit exceeded |
| 8711 | /api/v2/products/ | PUT | 404 | 33.8 | 876 | 2026-01-10 09:03:00 | Internal server error |
| 8790 | /api/v1/users | DELETE | 500 | 37.5 | NULL | 2026-02-11 10:10:00 | Bad gateway |
Expected output
| user_id | endpoint | failed_call_time | retry_call_time |
|---|---|---|---|
| 100 | /api/v1/payments | 2026-06-15 14:00:00 | 2026-06-15 14:04:00 |
| 100 | /api/v1/payments | 2026-06-15 14:02:00 | 2026-06-15 14:04:00 |
| 294 | /api/v1/orders | 2026-06-15 11:30:00 | 2026-06-15 11:33:00 |
Worked solution and explanation
What this really is
This is a bounded, forward-only pairing of a table with itself, dressed up as incident forensics. Every row in the answer is two rows of api_calls glued together: the call that broke and the call that got through. Anyone can write the self-join. What separates candidates is putting each test on the right side of the pair: the 5xx test belongs to the earlier call, the clean-response test to the later one. Swap them, or write one combined status filter, and you report users still stuck in a loop of failing retries as rescued, and the postmortem says the incident healed minutes before it did.
Build the pair
Step 1: Alias the table as failed and retry
Read api_calls twice and give the copies names that say what each row is: failed and retry. Aliases like a and b are exactly how people end up testing a.err_msg when they meant the retry. Equality on user_id and endpoint scopes every pair to one user hammering one endpoint.
Step 2: Bound the window forward
retry.call_time > failed.call_time makes the retry strictly later, so no call pairs with itself. julianday returns days, so the gap times 24 * 60 is minutes, and <= 5 keeps a call landing exactly on the five-minute mark.
Step 3: Qualify each side on its own terms
failed.status >= 500 keeps server-side failures only; a 404 or 401 is the client's problem and is not what a retry storm is made of. retry.err_msg IS NULL keeps only retries that came back clean. Two tests, two different rows.
The solution
SELECT
failed.user_id,
failed.endpoint,
failed.call_time AS failed_call_time,
retry.call_time AS retry_call_time
FROM api_calls AS failed
JOIN api_calls AS retry
ON retry.user_id = failed.user_id
AND retry.endpoint = failed.endpoint
AND retry.call_time > failed.call_time
AND (julianday(retry.call_time) - julianday(failed.call_time)) * 24 * 60 <= 5
WHERE failed.status >= 500
AND retry.err_msg IS NULLfailed carries the 5xx test, retry carries the clean-response test.
- One side tested
- Filtering only
failed.status >= 500returns every later call within five minutes, including retries that hit the same 503 again. Those rows say the user recovered when they were still failing. - Both sides tested
- Adding
retry.err_msg IS NULLkeeps only the call that actually got through, so each row is a real recovery and the time gap is the true time to recover.
Common follow-up questions
- Return only the first clean retry after each failure. What changes? (Tests whether they see that the pairing returns every later clean call, not the first one.)
- How would you report the median minutes to recover per `endpoint`? (Tests the time-to-recover framing: one row per failure, minimum gap.)
- What if a retry must also use the same `method` as the failed call? (Tests requirement clarification on the join key.)