Noisy Endpoints

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
L3

Problem

Error alerts during last night's incident keep pointing at a handful of API routes, so the on-call engineer wants every endpoint in api_calls that failed more than 5 times, where any response code of 400 or above counts as a failure. Show each one's failure count alongside its average latency across just those failed calls.

The routes generating the most noise.

Tables

api_calls

Columns: call_id INTEGER, endpoint TEXT, method TEXT, status INTEGER, latency REAL, user_id INTEGER, call_time TEXT, err_msg TEXT

api_calls: 10 of 670 rows
call_idendpointmethodstatuslatencyuser_idcall_timeerr_msg
8079/api/v1/ordersPOST2004.21002026-02-02 01:07:00NULL
8158/api/v2/productsPUT2007.91972026-03-03 02:14:00NULL
8237/api/v1/searchDELETE20111.62942026-04-04 03:21:00NULL
8316/api/v1/auth/loginPATCH20415.33912026-05-05 04:28:00NULL
8395/api/v1/auth/logoutget30119NULL2026-06-06 05:35:00NULL
8474/api/v2/analyticspost40022.75852026-07-07 06:42:00Not found
8553/api/v1/paymentsGET40126.46822026-08-08 07:49:00Unauthorized
8632/api/v1/users/POST40330.17792026-09-09 08:56:00Rate limit exceeded
8711/api/v2/products/PUT40433.88762026-01-10 09:03:00Internal server error
8790/api/v1/usersDELETE50037.5NULL2026-02-11 10:10:00Bad gateway

Expected output

First 5 of 8 rows
endpointfailure_countavg_latency
/api/v1/auth/login16516.05625
/api/v1/auth/logout13624.5076923076923
/api/v1/orders14721.6214285714286
/api/v1/payments7446.57142857142856
/api/v1/search13698.2538461538461

Worked solution and explanation

A two-stage filter in an incident costume

This is a two-stage filter wearing an incident-triage costume. The row-level cut, status >= 400, isolates the failed calls; the group-level cut, more than 5 of them, can only be applied after the rows are grouped, which is exactly what HAVING exists for. The trap: candidates write WHERE COUNT(*) > 5 and hit an aggregate-not-allowed-in-WHERE error, or drop the threshold entirely and bury the real hotspots under every route that failed a handful of times.


Break down the requirements

Step 1: Filter to error responses

WHERE status >= 400 keeps only failed API calls, before any grouping happens. status is an integer, so one range check catches 4xx and 5xx together; a string match on '4' would silently drop every 5xx.

Step 2: Group by endpoint and aggregate

GROUP BY endpoint with COUNT(*) for the failure count and AVG(latency) for the average latency. AVG ignores calls with a NULL latency, so it reports the mean of the values that actually exist.

Step 3: Apply the HAVING threshold

HAVING COUNT(*) > 5 runs against the aggregate itself, dropping every endpoint with 5 or fewer failures. That includes the stray padded paths and trailing-slash variants that only fail once or twice. WHERE cannot express this, because the count does not exist until the groups are formed.

Step 4: Leave the order alone

The grader compares the set of rows, not their sequence, and nothing in the ask commits to an order. Adding a sort is harmless for a human reader, but it is not part of the answer.


The solution

Filter on status, group, then filter groups
SELECT
    endpoint,
    COUNT(*) AS failure_count,
    AVG(latency) AS avg_latency
FROM api_calls
WHERE status >= 400
GROUP BY endpoint
HAVING COUNT(*) > 5
Hand-rolled mean
SUM(latency) / COUNT(*) divides by every failed row, including the ones whose latency is NULL, so the result is pulled low.
Built-in mean
AVG(latency) sums and counts only the non-null latency values, which is the average of what was actually measured.

Common follow-up questions