After the Handshake

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
Asked in
Uber

Problem

On the platform team's latency board, endpoints look faster than they are because every user's earliest call in api_calls lands quickly, so those opening calls should not count. Report each endpoint's average latency over the remaining calls as a whole number of milliseconds with the fraction dropped, slowest endpoint first.

The first call is a promise. Judge the ones that follow.

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 12 rows
endpointavg_repeat_latency_ms
/api/v1/payments335
/api/v1/search282
/api/v1/users271
/api/v1/orders269
/api/v2/products262

Worked solution and explanation

What this is really asking

This is a per-user 'drop the opener' problem dressed up as a latency report. The skill being probed is whether you can remove exactly one row per user, chosen by time, and then regroup what is left by a completely different key. Anyone can write the per-endpoint average. What separates candidates is where they scope 'earliest': per user, not per user and endpoint, and not once for the whole table. Scope it per user and endpoint and you drop an opener on every endpoint a user touched, which is most of their history, since each user's 4 to 13 calls spread across many endpoints. Scope it globally and you drop one row, and the board still flatters every endpoint.

Step 1: Number each user's calls in time order

ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY call_time) restarts at 1 for every user. The opener is the row numbered 1, and every repeat call is 2 or higher. call_time is ISO text, so it sorts chronologically as stored. Calls with no user_id share one partition and lose a single opener between them.

Step 2: Filter on the number one level up

WHERE runs before window functions, so call_seq > 1 cannot sit beside the SELECT that defines call_seq. Wrap the numbering in a subquery and filter in the outer query.

Step 3: Average by endpoint, then drop the fraction

Group the surviving rows by endpoint and take AVG(latency). Calls with no recorded latency are skipped by the average. The board shows whole milliseconds, so CAST(AVG(latency) AS INTEGER) truncates: 335.8 becomes 335, not 336. Sort by that figure, highest first.

The solution

Average repeat-call latency per endpoint
SELECT endpoint,
       CAST(AVG(latency) AS INTEGER) AS avg_repeat_latency_ms
FROM (
  SELECT endpoint,
         latency,
         ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY call_time) AS call_seq
  FROM api_calls
) numbered_calls
WHERE call_seq > 1
GROUP BY endpoint
ORDER BY avg_repeat_latency_ms DESC
Opener per user and endpoint
PARTITION BY user_id, endpoint numbers each user's calls separately on every endpoint, so a user who hit six endpoints loses six calls. Most histories shrink sharply and every average moves.
Opener per user
PARTITION BY user_id alone numbers the whole history, so exactly one call per user leaves the set, whichever endpoint it happened to hit.

Follow-up questions

Common follow-up questions