The Slow Creep
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
- ESPN
Problem
Our CI builds feel like they have been creeping slower year after year, and we want to see where that is happening repository by repository. From ci_builds, give the average build duration for each repository and calendar year, unrounded and in seconds, slowest average first.
Every year the builds got heavier. Find where the minutes went.
Tables
ci_builds
Columns: build_id INTEGER, repo_name TEXT, branch TEXT, status TEXT, dur_secs INTEGER, trigger TEXT, built_at TEXT
| build_id | repo_name | branch | status | dur_secs | trigger | built_at |
|---|---|---|---|---|---|---|
| 5047 | backend-api | develop | failed | 34 | pull_request | 2026-02-02 01:13:00 |
| 5094 | infra | feature/auth | canceled | 53 | schedule | 2026-03-03 02:26:00 |
| 5141 | ml-pipeline | fix/login-bug | running | 72 | manual | 2026-04-04 03:39:00 |
| 5188 | mobile-app | release/v2.0 | queued | 91 | tag | 2026-05-05 04:52:00 |
| 5235 | data-platform | hotfix/payment | success | 110 | webhook | 2026-06-06 05:05:00 |
| 5282 | frontend | feature /spaces | failed | NULL | push | 2026-07-07 06:18:00 |
| 5329 | backend-api | main | canceled | 148 | pull_request | 2026-08-08 07:31:00 |
| 5376 | infra | develop | running | 167 | schedule | 2026-09-09 08:44:00 |
| 5423 | ml-pipeline | feature/auth | queued | 186 | manual | 2026-01-10 09:57:00 |
| 5470 | mobile-app | fix/login-bug | success | 205 | tag | 2026-02-11 10:10:00 |
Expected output
| repo_name | build_year | avg_duration |
|---|---|---|
| data-platform | 2023 | 608 |
| mobile-app | 2022 | 589 |
| infra | 2025 | 551 |
| backend-api | 2024 | 532 |
| ml-pipeline | 2026 | 471.9 |
Worked solution and explanation
What this problem really is
Under the CI-metrics costume, this is a two-dimensional grouping problem where one of the two dimensions does not exist yet. Anyone can group by repo and take AVG(dur_secs). What separates candidates is that the year is not stored anywhere: it has to be derived from built_at, a TEXT timestamp. Group by the raw string and every build becomes its own group. Leave the year out and you get one number per repo, which hides the creep entirely. The quieter second test is the missing durations. How your average treats the builds with no dur_secs decides whether every figure is right or quietly too low.
Break down the requirements
Step 1: Pull the year from a text timestamp
built_at is stored as TEXT, not a native timestamp, so derive the year from it: STRFTIME('%Y', built_at) returns the four-digit year. Whatever date-part expression you choose, use the same one in the select list and the GROUP BY, so the label you show is the bucket you grouped on.
Step 2: Average duration per repo per year
Group on the pair (repo_name, derived year) and take AVG(dur_secs). Grouping on both dimensions is the whole point: drop either one and you have answered a different question.
Step 3: Sort slowest first, keep it unrounded
Order by avg_duration DESC so the slowest repository-years sit on top. That is the whole ordering the question asks for: two equal averages may come back in either order and both are correct. Report the average exactly as AVG returns it, in seconds. Rounding it to tidy the output changes the number being graded.
The solution
SELECT
repo_name,
STRFTIME('%Y', built_at) AS build_year,
AVG(dur_secs) AS avg_duration
FROM ci_builds
GROUP BY repo_name, STRFTIME('%Y', built_at)
ORDER BY avg_duration DESCCommon follow-up questions
- How would you report the P95 build duration alongside the average? (Tests whether they reach for `PERCENTILE_CONT(0.95)` and know which engines support it, or fall back to a ranking workaround.)
- What if you only wanted the repos that got slower year over year? (Tests window functions: `LAG(avg_duration)` partitioned by `repo_name`, ordered by `build_year`, then compare consecutive years.)
- How would you restrict this to only successful builds? (Tests where the filter belongs: a `WHERE status = 'success'` before grouping, not a `HAVING` after.)