What's in a Name
An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- easy
- Seniority
- L3
Problem
Find how many usernames in users begin with each letter and what percentage of all users each letter accounts for, most common letters first. Some usernames were saved with a stray leading space or a capital first letter, and those users still belong under their letter.
The first letter is a fingerprint. Read the crowd by it.
Tables
users
Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT
| user_id | username | signup_date | account_status | age_bucket | |
|---|---|---|---|---|---|
| 100 | alice | alice@example.com | 2025-02-02 | inactive | 25-34 |
| 197 | aaron42 | aaron42@example.com | 2026-03-03 | suspended | 35-44 |
| 294 | amelia | amelia@example.com | 2024-04-04 | pending_verification | 45-54 |
| 391 | arjun | arjun@example.com | 2025-05-05 | active | 55-64 |
| 488 | ava99 | ava99@example.com | 2026-06-06 | inactive | 65+ |
| 585 | andrew_k | andrew_k@example.com | 2024-07-07 | suspended | NULL |
| 682 | anika | anika@example.com | 2025-08-08 16:34:00 | pending_verification | 18-24 |
| 779 | aiden | aiden@example.com | 2026-09-09 14:53:00 | active | 25-34 |
| 876 | aria_b | aria_b@example.com | 2024-10-10 | inactive | 35-44 |
| 973 | brian | brian@example.com | 2025-11-11 | suspended | 45-54 |
Expected output
| initial | user_count | pct |
|---|---|---|
| a | 17 | 8.5 |
| m | 14 | 7 |
| c | 13 | 6.5 |
| s | 12 | 6 |
| j | 12 | 6 |
Worked solution and explanation
What this really is
This is a share-of-total problem wearing a head-count costume. Counting users per letter is the easy half. Once you group, each row only knows its own letter's count, and the percentage needs the count of every user. Bring the grand total into the grouped result and divide in decimal arithmetic. Divide by the group's own count and every letter reads 100. Divide two integers and 17 out of 200 truncates to 8, or to 0 if the division runs before the multiply.
Build it
Step 1: Clean the initial before you use it
username was not cleaned on entry: ' amelia' carries a leading space and 'Alice' a capital. SUBSTR(username, 1, 1) alone gives them their own rows, ' ' and 'A'. LOWER(SUBSTR(TRIM(username), 1, 1)) trims, takes the first character and lowercases it, so both land under 'a'.
Step 2: Group on the same expression
Repeat the full expression in GROUP BY. Some engines accept the initial alias there, but the expression form runs everywhere and makes it plain that the grain is the cleaned letter, not the raw column.
Step 3: Bring in the grand total
(SELECT COUNT(*) FROM users) returns one number, and every group row reads it. Multiplying by 100.0 first makes the arithmetic decimal, so 17 of 200 comes out as 8.5, not 8.
Step 4: Most common first
ORDER BY user_count DESC is the whole ordering. Letters with equal counts, like 'j' and 's' at 12, may come back either way round, and both orders are correct.
SELECT
LOWER(SUBSTR(TRIM(username), 1, 1)) AS initial,
COUNT(*) AS user_count,
100.0 * COUNT(*) / (SELECT COUNT(*) FROM users) AS pct
FROM users
GROUP BY LOWER(SUBSTR(TRIM(username), 1, 1))
ORDER BY user_count DESCSELECT
LOWER(SUBSTR(TRIM(username), 1, 1)) AS initial,
COUNT(*) AS user_count,
COUNT(*) / (SELECT COUNT(*) FROM users) * 100 AS pct
FROM users
GROUP BY LOWER(SUBSTR(TRIM(username), 1, 1))
ORDER BY user_count DESCCOUNT(*) / (SELECT COUNT(*) FROM users) is 0 for every letter before the * 100 ever runs.
SELECT
LOWER(SUBSTR(TRIM(username), 1, 1)) AS initial,
COUNT(*) AS user_count,
100.0 * COUNT(*) / SUM(COUNT(*)) OVER () AS pct
FROM users
GROUP BY LOWER(SUBSTR(TRIM(username), 1, 1))
ORDER BY user_count DESCSUM(COUNT(*)) OVER () adds up the 26 grouped counts instead of rereading users.
Common follow-up questions
- If `username` could be `NULL`, what happens to that group and to the denominator? (`TRIM(NULL)` stays `NULL`, so those users form a `NULL` initial while `COUNT(*)` still counts them in the total.)
- Some usernames start with a digit or an underscore. Should those get their own rows or share an 'other' bucket? (Tests whether the candidate settles the grain before reaching for a `CASE`.)
- If each `pct` were rounded to one decimal, would the column still add up to exactly 100.0? (Independent per-row rounding can leave the total at 99.9 or 100.1.)