What SQL and Python interviews look like
Companies mix a few formats. Knowing which one you face changes how you prepare, so ask the recruiter early: which database or dialect, can I run my code, and is anyone watching while I type?
Online assessment
Timed (often 45 to 90 minutes), on a platform, graded automatically against hidden tests. Nobody sees how you think, so correctness and edge cases decide everything. Read every note in the task: the answer is often hiding in a line about NULLs, ties or which statuses count.
Live coding in a shared editor
30 to 60 minutes with an interviewer, one to three questions that usually escalate. You talk while you type. Sometimes the editor cannot execute anything, so practise writing a query and checking it by reading, not only by running.
Take-home
Hours or days, a dataset plus questions or a small app. Judged on correctness, clear assumptions, readable code, and often tests or a short write-up. Treat the README as part of the answer.
Verbal or whiteboard
No editor: “design a schema for this”, “why is this query slow”, “what is the difference between these two isolation levels”. Short, conceptual, and a good place to show you have seen real systems.
A typical loop: recruiter screen, then an online assessment or a technical phone screen, then one or two live technical rounds (SQL, sometimes Python or a case), and finally a behavioural or team-fit conversation. Loops vary a lot between companies; treat this as a map, not a promise.
How it differs by role
| Analyst (data, BI, product) | Data engineer | Backend developer | |
|---|---|---|---|
| SQL focus | Business questions: retention, funnels, cohorts, month-over-month change, top-N per group, de-duplication. Window functions, dates and CASE are everywhere. | The same, plus joins at scale, incremental loads, de-duplication, slowly changing data, reading query plans. | Moderate: joins, aggregation, subqueries, usually on a schema you are asked to design or extend. |
| Python + databases | Rare as a database topic. You will more often see pandas, which this guide does not cover. | Common: ETL scripts, batching, transactions, idempotent loads, migrations. | Common: schema design, constraints, transactions and isolation, indexes, N+1, injection, migrations. |
| What stands out | Asking what a metric means before you compute it, and sanity-checking the number. | Thinking about re-runs, failures, volume and cost. | Thinking about concurrency, data integrity and how the code behaves under load. |
None of these are hard rules. A backend interview can include window functions, and an analyst loop can include a schema question. Prepare the core for your role, then cover the neighbours lightly.
What interviewers evaluate
Most rubrics boil down to six things. A correct query that you cannot explain scores lower than a nearly correct one that you built carefully and tested out loud.
- Correctness. The result matches the question, including the cases that are easy to forget. Right grain, right columns, right order.
- Edge cases. NULLs, ties, duplicates, empty groups, date boundaries, “what if a user has no orders?”. Naming these before you are asked is the strongest signal at junior and mid level.
- Communication. You restate the problem, ask useful questions, narrate your plan, and say what you are unsure about.
- Structure. You build the answer in steps (CTEs with sensible names) instead of one 40-line nested query nobody can follow.
- Performance awareness. You can say roughly what the database will do, where an index would help and what changes at a hundred times the data. You do not need to optimise prematurely.
- Code clarity. Consistent aliases, explicit join conditions, no
SELECT *in a final answer, one idea per CTE.
First a correct answer, then one you can explain, then a clean one, then a fast one. Cleverness is last. A readable CTE beats a clever one-liner every time.
A step-by-step framework for solving an SQL question live
When the clock is running, a fixed routine keeps you from freezing. Use these five steps every time, even on easy questions, until they are automatic.
- Clarify the questionAsk before typing. At minimum settle: the grain (one row per what?), NULLs (can the column be empty, and what should happen then?), ties (if two rows share the top value, do we show both?), duplicates (can the same event appear twice?), date bounds (inclusive or exclusive, which timezone, what is “last 30 days” relative to?), and definitions (“active”, “revenue”: which statuses count?).
- Define the outputWrite down the column list, the grain and the sort order before any SELECT. Sketch two or three rows of expected output. If you cannot, you have not understood the question yet.
- Build it step by stepStart with the smallest correct base (usually one join), look at it, then add one thing at a time as CTEs: filter, aggregate, rank, final select. Name each CTE for what it holds. After each step ask “how many rows should this have?”.
- Check the edge casesWalk a tiny example through the query out loud. Test the empty group, the NULL, the tie, the boundary date and the duplicate. This is where most lost points are recovered.
- Discuss performance and alternativesSay what the database does (scans, joins, sorts), which index would help, whether a window function or a self-join or an EXISTS would also work, and what you would change at a hundred times the data.
Worked example
The schema is invented for this guide. Dates are text in ISO format, money is stored in cents.
users(user_id, name, country, signed_up_at) -- signed_up_at: 'YYYY-MM-DD'
payments(payment_id, user_id, amount_cents, status, paid_at)
-- status: 'paid' | 'failed' | 'refunded'
-- paid_at: 'YYYY-MM-DD HH:MM:SS'
“For every country, find the user who spent the most in their first 30 days after signing up. Only count successful payments.”
1. Clarify
- Grain: one row per country. If two users tie for the top, do we return both? Assume yes, both.
- “First 30 days”: does a payment exactly 30 days after sign-up count? Assume not: the window is the sign-up day plus the next 29 days (days 0 to 29 after sign-up), so “less than sign-up date + 30 days”.
- “Successful”: only
status = 'paid'; refunded and failed are ignored. - Users with no payments, and countries where nobody paid: not shown.
- Amount: report in currency units, not cents, with two decimals.
2. Define the output
country, user_id, spend: one row per country (more only on a tie), sorted by country then user_id.
3. Build
WITH first_30 AS ( -- payments inside each user's window
SELECT u.user_id, u.country, p.amount_cents
FROM users u
JOIN payments p ON p.user_id = u.user_id
WHERE p.status = 'paid'
AND date(p.paid_at) >= u.signed_up_at
AND date(p.paid_at) < date(u.signed_up_at, '+30 days')
),
per_user AS ( -- one row per user
SELECT user_id, country, SUM(amount_cents) / 100.0 AS spend
FROM first_30
GROUP BY user_id, country
),
ranked AS ( -- best spender first, ties share rank 1
SELECT user_id, country, spend,
RANK() OVER (PARTITION BY country ORDER BY spend DESC) AS rnk
FROM per_user
)
SELECT country, user_id, printf('%.2f', spend) AS spend
FROM ranked
WHERE rnk = 1
ORDER BY country, user_id;
Each CTE answers one question: which payments count, how much each user spent, who is first in their country. SQLite syntax is shown; in PostgreSQL the window bound would be p.paid_at < u.signed_up_at + INTERVAL '30 days'. printf('%.2f', spend) prints exactly two decimals (as text); in PostgreSQL use ROUND(spend, 2).
4. Check the edge cases
With a handful of invented rows (Ana and Bo in KZ, Cy and Di in DE, Ed in FR. Ana signed up on 5 January: 30.00 on the sign-up day, 20.00 on day 29 (3 February) and 99.00 on day 30 (4 February); Bo: one 45.00 payment and one refunded 50.00; Cy and Di both with 40.00; Ed with no payments) the query returns:
| country | user_id | spend |
|---|---|---|
| DE | 3 | 40.00 |
| DE | 4 | 40.00 |
| KZ | 1 | 50.00 |
Ana's day-30 payment is correctly left out (50.00, not 149.00), Bo's refund is ignored, the tie in DE shows both users, and Ed's country FR does not appear. Say each of these out loud as you check it.
5. Discuss
- Ties:
RANKkeeps both tied users. If the business wants exactly one, switch toROW_NUMBER()withORDER BY spend DESC, user_idso the result is deterministic. - Performance: an index on
payments(user_id, paid_at)supports the join, and the date filter can use it only ifpaid_atis compared raw:p.paid_at >= u.signed_up_at AND p.paid_at < date(u.signed_up_at, '+30 days'), with no function wrapped around the column as in the query above. On large data I would write it that way. - Alternative: skip the ranking and compare each spend to
MAX(spend)per country. Same answer, but the window version extends naturally to “top 3 per country”.
Python + databases: topics and how to answer
These come up for backend and data-engineering roles, usually as a mix of “write it” and “explain it”. Each topic below lists what to know, how to talk about it, and problems to practise on. Examples use sqlite3 from the standard library, the same setup the QueryGym Python track uses.
Schema design and normalisation trade-offs
Start from entities and relationships, give every table a key, and push repeated facts into their own table so each fact lives in one place. Normalising (roughly third normal form) prevents update anomalies. Denormalising is a deliberate trade: faster reads or simpler reporting in exchange for keeping copies in sync.
- Name the entities and the one-to-many and many-to-many links before drawing tables. A many-to-many link needs a join table.
- Show you know when a copy is correct:
order_items.unit_pricestores the price at the time of sale. It is history, not duplication. - When you denormalise, say what keeps it consistent (a trigger, a job, a view) and what the failure mode is.
Practise: Design Users and Orders Tables Py free, Students, Courses and Enrollments Py free, Normalise a Flat Orders Table Py Pro
Constraints
Primary keys, foreign keys, UNIQUE, NOT NULL and CHECK let the database refuse bad data, which application code alone cannot guarantee when there is more than one writer. In SQLite, foreign keys are off until you run PRAGMA foreign_keys = ON on each connection, a classic gotcha worth mentioning.
- “I would enforce invariants in the database and also validate in the app for friendly errors.”
- For soft deletes, a plain
UNIQUE(email)blocks re-registration; a partial unique index (WHERE deleted_at IS NULL) allows it. - Say what happens on delete:
RESTRICT,CASCADEorSET NULL, and why.
Practise: Soft-Deleted Accounts with Reusable Emails Py Pro, Price History Kept by a Trigger Py Pro
Transactions, ACID and isolation
A transaction makes a group of statements atomic: all of them happen or none do. ACID is atomicity, consistency, isolation and durability. Isolation levels trade safety for concurrency: weaker levels allow dirty reads, non-repeatable reads, phantoms and lost updates; serializable behaves as if transactions ran one at a time. Defaults differ by database (PostgreSQL defaults to read committed), and SQLite serialises writers.
- Use a concrete race: two withdrawals read the same balance and both succeed. Then give the fix: a single
UPDATE ... WHERE balance >= :amountand a row-count check, a row lock, or an optimistic version column. - In
sqlite3,with conn:commits on success and rolls back on an exception, but in the default mode it opens a transaction only before INSERT / UPDATE / DELETE: CREATE, ALTER and PRAGMA run in autocommit. Start one explicitly (conn.execute("BEGIN"), orsqlite3.connect(..., autocommit=False)on Python 3.12+), and remember thatexecutescript()commits first. Mention savepoints for partial rollback inside a larger batch. - For payments and imports, add idempotency: a unique request key so a retry cannot apply twice.
Practise: Atomic Money Transfer Py free, Idempotent Payment Recording Py Pro, Batch Import with Savepoints Py Pro
Indexes and EXPLAIN QUERY PLAN
An index is a sorted structure (usually a B-tree) that lets the database find rows without scanning the table. It speeds reads and slows writes, and it costs space. In a composite index, put equality columns first and the range column last. A function on the column (WHERE date(created_at) = ...) or a leading wildcard (LIKE '%abc') usually prevents index use.
- Run
EXPLAIN QUERY PLANfirst and read it:SCANmeans a full pass;SEARCH ... USING INDEXmeans the index is used. - Reason about selectivity: an index on a column with two distinct values rarely helps.
- Offer a covering index when a hot query only needs a few columns, and name the write-cost trade-off.
Practise: Index the Hot Queries Py free, Keyset Pagination for Orders Py Pro
The N+1 problem
N+1 is one query to fetch a list and then one more query per row to fetch something related: 101 round trips for 100 customers. It is invisible on a small dev database and painful in production.
- Detect it by counting queries per request or reading the query log.
- Fix it with one join, or two queries where the second uses
WHERE id IN (...)for the whole batch. In ORMs this is eager loading (selectinloadorjoinedloadin SQLAlchemy,select_relatedorprefetch_relatedin Django). - Mention the trade-off: a join over a one-to-many link repeats the parent row, so the batch query is sometimes the better fix.
Practise: Fix the N+1 Customer Report Py Pro, Employees as Dictionaries Py Pro
SQL injection and parameterised queries
Building SQL by pasting user input into a string lets that input change the query. The fix is parameters: you send the SQL text and the values separately (? in sqlite3, %s in psycopg), so values can never become code.
# wrong: the input becomes part of the SQL
conn.execute(f"SELECT * FROM products WHERE name LIKE '%{term}%'")
# right: the value travels separately
conn.execute("SELECT * FROM products WHERE name LIKE ?", (f"%{term}%",))
- Table and column names cannot be parameters; if they come from users, check them against an allow-list.
- Escape
%,_and the escape character itself with an ESCAPE clause (LIKE ? ESCAPE '\') if the user's text must be matched literally inLIKE. - Parameters also help the database reuse query plans, so they are the default even when input is trusted.
Practise: Safe Product Search Py free, All-or-Nothing Customer Import Py Pro
Migrations
A migration is a versioned, ordered change to the schema. A runner records which versions have been applied (in SQLite, PRAGMA user_version is a simple place for that) and applies the rest in order.
- Never edit a migration that has shipped; add a new one.
- Run each migration in a transaction where DDL is transactional (SQLite and PostgreSQL yes, MySQL no) so a failure leaves the database untouched. In the default
sqlite3mode a migration that mixes DDL and DML is not automatically atomic (the DDL runs in autocommit), so use an explicitBEGIN...COMMIT(notexecutescript(), which commits first), checkPRAGMA user_versioninside it and set the new version before the commit. - For zero-downtime changes use expand and contract: add the new column, write to both, backfill in batches, switch reads, then drop the old one.
Practise: A Migration Runner on PRAGMA user_version Py Pro, Normalise a Flat Orders Table Py Pro, Subscription Repository Py Pro
13 classic mistakes (and the problems that train them)
These cost people points again and again. Each one has an example and problems where it actually bites. Keep this list as your pre-submit checklist.
1Comparing to NULL with =
NULL = NULL is not true, it is unknown, so the row is dropped. WHERE end_date = NULL returns nothing, and status <> 'x' silently drops rows where status is NULL.
WHERE end_date IS NULL
WHERE status IS DISTINCT FROM 'x' -- standard SQL (PostgreSQL, SQLite 3.39+); older SQLite: status IS NOT 'x'
Train it: Projects With No End Date Pro, Every employee and their manager (or none) Pro
2NOT IN with a NULL in the subquery
x NOT IN (1, 2, NULL) is never true, so a single NULL in the subquery empties the whole result. Prefer NOT EXISTS, or filter the NULLs out.
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id)
Train it: Users With No Active Subscription Pro, Users Who Never Subscribed Pro
3COUNT(*) versus COUNT(column)
After a LEFT JOIN, an unmatched parent still produces one row, so COUNT(*) reports 1 where the truth is 0. COUNT(child.id) skips NULLs and gives 0.
SELECT d.name, COUNT(e.employee_id) AS headcount -- not COUNT(*)
FROM departments d LEFT JOIN employees e ON e.department_id = d.department_id
GROUP BY d.department_id, d.name;
Train it: Department Headcount free, Headcount by Department (Including Empty Ones) free
4WHERE versus HAVING
WHERE filters rows before grouping and cannot see aggregates; HAVING filters groups after. Filter rows early in WHERE whenever you can, because it is both clearer and cheaper.
SELECT customer_id, COUNT(*) AS n
FROM orders
WHERE status = 'completed' -- rows
GROUP BY customer_id
HAVING COUNT(*) > 3; -- groups
Train it: Repeat Buyers free, Power Users for the Beta free
5A LEFT JOIN silently turned into an INNER JOIN
A WHERE condition on the right-hand table removes the unmatched rows (where its columns are NULL), which undoes the LEFT JOIN. Put conditions about the right-hand table in ON.
-- loses plans with no active subscription
FROM plans p LEFT JOIN subscriptions s ON s.plan_id = p.plan_id
WHERE s.status = 'active'
-- keeps every plan
FROM plans p LEFT JOIN subscriptions s ON s.plan_id = p.plan_id AND s.status = 'active'
Train it: Export Usage by Plan Pro, Customers Without a Completed Order Pro
6Join fan-out: duplicated rows inflate the numbers
Joining two one-to-many tables onto the same parent multiplies rows (3 subscriptions times 5 events is 15 rows), so SUM and COUNT come out too high. Aggregate each side first in a CTE, or count distinct keys. Adding DISTINCT to the outer SELECT usually hides the problem rather than fixing it.
Train it: Export Usage by Plan Pro, Products Bought Together Pro
7Integer division
In SQLite and PostgreSQL, 3 / 2 is 1, so completed / total comes out as 0 (or 1), and 100 * completed / total is truncated to a whole number. Force decimals by multiplying by 100.0 first, and guard against dividing by zero with NULLIF.
ROUND(100.0 * completed / NULLIF(total, 0), 1)
Train it: Completed-order rate per customer Pro, Signup-to-Paid Funnel by Country Pro
8Ties in ranking
ROW_NUMBER breaks ties arbitrarily, RANK gives ties the same rank and skips the next, DENSE_RANK does not skip. “Top 3” can mean three rows or three ranks; ask. A LIMIT 1 on a tied maximum quietly hides a user.
Train it: Ranking Customers by Completed Orders: RANK vs DENSE_RANK Pro, Highest-paid direct report per manager Pro
9ORDER BY without a tie-breaker, with LIMIT or OFFSET
If the sort key is not unique, the database may return tied rows in any order, so page 3 can overlap page 2 and a top-5 can change between runs. End the ORDER BY with a unique column.
ORDER BY order_date DESC, order_id DESC LIMIT 10 OFFSET 20
Train it: Third Page of the Order List free, Five Priciest Products free
10Inclusive versus exclusive date ranges
BETWEEN includes both ends. With timestamps, paid_at BETWEEN '2024-12-01' AND '2024-12-31' misses everything after midnight on the 31st (in SQLite with text timestamps, the entire day). Use a half-open range: start inclusive, next period's start exclusive.
WHERE paid_at >= '2024-12-01' AND paid_at < '2025-01-01'
Train it: First-Quarter Signups free, Joiners of the Last Twelve Months Pro
11Counting the wrong rows
Revenue usually means completed orders only; “active” often has a precise definition; cancelled and refunded rows may or may not count. Skipping the clarifying question gives a number that is confidently wrong. Put the rule in a named CTE so it is visible.
Train it: Top 5 Customers by Completed Revenue Pro, Monthly Realized Revenue Pro
12LIMIT for “top N per group”
LIMIT 2 returns two rows in total, not two per category. Rank inside each group with a window function, then filter in an outer query (window results cannot be used in the same WHERE).
WITH r AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY revenue DESC, product_id) AS rn
FROM product_revenue)
SELECT * FROM r WHERE rn <= 2;
Train it: Top 2 Products by Revenue in Each Category Pro, Latest Completed Order per Customer free
13Aggregates quietly skip NULLs
AVG, SUM and COUNT(col) ignore NULLs, so employees with no assignments vanish from an average that was supposed to include them. Decide whether “missing” means zero, then say so with COALESCE. An average of averages is not the overall average either.
Train it: Average Project Load per Department Pro, Users by Subscription Status Pro
Communication: how to think out loud
Interviewers hire people they can work with. Silence reads as being stuck, even when you are not.
Thinking aloud
- Restate the task in your own words: “So I need one row per country, the top spender in their first 30 days. Is that right?”
- Announce the plan before the code: “I will first filter the payments to each user's window, then total per user, then rank within country.”
- Narrate decisions, not keystrokes: “I will use a LEFT JOIN here because customers with no orders must still appear.”
- Flag what you are unsure about instead of hiding it: “I do not remember if this function exists in this dialect; I will write it the way I think it works and we can adjust.”
Clarifying questions worth asking
- What is one row in the result? What is the grain of each table?
- Can this column be NULL? Can a user have no orders?
- If two rows tie, should I return both or pick one?
- Are there duplicate rows, or is this key unique?
- Are date ranges inclusive? Which time zone? Relative to what “today”?
- Which statuses count toward revenue or “active”?
- How big are the tables, and do I need to worry about performance?
- Which dialect am I writing for, and can I run the query?
When you are stuck
- Say where you are: “I know I need the latest order per customer, and I am deciding between a window function and a correlated subquery.”
- Shrink the problem: solve it for one customer, then generalise.
- Write the sub-query you are sure about and look at its output.
- Try a tiny example by hand and see which row you would pick and why.
- After a minute or two of no progress, ask for a nudge: “Could you confirm I should be looking at window functions?” Asking early costs less than silence.
Testing your query out loud
Do not just run it and say “looks right”. Pick three or four rows and trace them: “Ana signed up on 5 January, her payment on 6 January is inside the window, the one on 4 February is day 30, so it is out.” Then name the edge case you are checking each time.
Taking hints well
- A hint is not a failure; it is the interviewer helping you show what you can do. Thank them and use it.
- Restate the hint in your own words, then apply it. That shows you understood it instead of copying it.
- Do not argue defensively. If you disagree, say why, calmly, with an example.
- If you got it wrong, say what you would do differently, then fix it. Recovering well is a strong signal.
Prep plans: 7, 14 or 30 days
Pick the plan that matches your time, not your ambition. Each day is one to two hours: a short read, two to four problems from easy to hard, and a review of what you got wrong. Problems that need Pro are tagged, and the early days of every plan use free problems so you can begin right away. Mock-interview checkpoints are highlighted: they are your honest progress test, so do them under the clock and without peeking at solutions. Junior and Middle mock interviews work with the free problems; a Senior mock (1 medium and 2 hard problems) needs Pro. The plans keep the free medium problems for after your first mock, so the mocks still have fresh problems to draw from.
- Try each problem for 10 to 15 minutes before asking for a hint. Then use the graded hints, then the walkthrough (walkthroughs unlock once you solve the problem, or with Pro).
- Keep a “mistake log”: one line per slip (for example, “forgot the tie-break”). Re-read it before every mock.
- If a day goes badly, repeat it instead of moving on. Skipping weak topics is how plans fail.
- Never skip the checkpoint days. A mock under pressure teaches things untimed practice cannot.
- Be realistic about what is free: the free set covers filtering, sorting and pagination, a string-label task, aggregation and three medium problems (a LEFT JOIN, a subquery and a window function); in Python it covers schema design, a transaction, safe queries and an index plan. Most joins, subqueries, CTEs, window functions and dates are Pro, so the later days of every plan need it.
7-day crash plan
For when the interview is next week and you already know basic SELECT. It covers the core in a fixed order: filtering, aggregation, joins, CTEs, windows, and a day for Python + databases, with two mock checkpoints. Days 1–2 and the Python day (day 6) use only free problems. The other days include Pro problems.
- Customers in Target Markets free
- Titles That Say Manager free
- First-Quarter Signups free
- Third Page of the Order List free
Read mistakes 1, 9 and 10 first.
- Repeat Buyers free
- MRR by Plan free
- Top 5 Products by Revenue free
- Power Users for the Beta free
Read mistake 4.
- Department Headcount free
- Users Who Never Subscribed Pro
- Top 5 Customers by Completed Revenue Pro
- Customers Without a Completed Order Pro
Read mistakes 2, 3, 5 and 6. Checkpoint: take a first short mock interview to find your baseline. Expect to struggle; that is the point.
- Employees Paid Above Their Department Average free
- Customers Above Average Completed Revenue Pro
- Completed-order rate per customer Pro
- Monthly Realized Revenue Pro
Read mistakes 7 and 11.
- Latest Completed Order per Customer free
- Ranking Customers by Completed Orders: RANK vs DENSE_RANK Pro
- Top 2 Products by Revenue in Each Category Pro
- Running Total of New Subscriptions by Month Pro
Read mistakes 8 and 12.
- Design Users and Orders Tables Py free
- Atomic Money Transfer Py free
- Safe Product Search Py free
- Index the Hot Queries Py free
Read the Python + databases section and practise saying each “how to talk about it” list aloud.
Checkpoint: take a full mock interview under the clock, then ask the AI tutor for feedback. Fix the top two items from your mistake log, then use the two stretch problems above only if you have energy left. Finish with the checklist.
14-day plan
The balanced plan. It adds a day per topic, two Python + databases days, a hard-problems day and three mock checkpoints. Good if you can give an hour or two a day. Days 1–3 use only free problems. Later days include Pro problems.
Read mistakes 1, 9 and 10.
- Repeat Buyers free
- Top 5 Products by Revenue free
- MRR by Plan free
Read mistakes 3 and 4. Checkpoint: take a first short mock interview and write down your baseline.
- Users Who Never Subscribed Pro
- Headcount by Department (Including Empty Ones) free
- Employees, Their Managers, and Departments Pro
- Top 5 Customers by Completed Revenue Pro
Read mistakes 5 and 6.
- Users With No Active Subscription Pro
- Customers Without a Completed Order Pro
- Employees Paid Above Their Department Average free
- Logged In, Never Used a Feature Pro
Read mistake 2.
- Customers Above Average Completed Revenue Pro
- Completed-order rate per customer Pro
- Headcount by salary band Pro
- Category Share of Completed Revenue Pro
Read mistakes 7, 11 and 13. Checkpoint: take a mock interview and compare it with day 3.
- Latest Completed Order per Customer free
- Ranking Customers by Completed Orders: RANK vs DENSE_RANK Pro
- Top 2 Products by Revenue in Each Category Pro
Read mistakes 8 and 12.
- Design Users and Orders Tables Py free
- Students, Courses and Enrollments Py free
- Soft-Deleted Accounts with Reusable Emails Py Pro
- Safe Product Search Py free
Read the schema, constraints and injection topics.
- Atomic Money Transfer Py free
- Idempotent Payment Recording Py Pro
- Index the Hot Queries Py free
- Fix the N+1 Customer Report Py Pro
Read the transactions, indexes and N+1 topics.
Then redo anything from your mistake log that you still get wrong.
Checkpoint: take two mock interviews (one SQL-heavy, one mixed), get the tutor's feedback on both, and finish with the checklist. The two problems above are stretch work if you still have energy.
30-day plan
The thorough plan, for career switchers and anyone starting from little. One theme per day in four weeks, with an early baseline mock on day 3, a mock checkpoint at the end of every week and a light final day. Days 1–4 use only free problems. Later days include Pro problems.
Week 1: foundations
Read mistake 10.
Read mistake 9. Checkpoint: take a first short mock interview to set your baseline. Expect to struggle; that is the point.
- Repeat Buyers free
- Top 5 Products by Revenue free
- Department Headcount free
Read mistakes 3 and 4.
- Pay Spread Within Shared Job Titles Pro
- Users by Subscription Status Pro
- Projects With No End Date Pro
Read mistakes 1 and 13.
Checkpoint: take a second mock interview and compare it with your day 3 baseline, then redo the two problems above if they felt shaky and update your mistake log.
Week 2: joins and subqueries
- Top 5 Customers by Completed Revenue Pro
- Export Usage by Plan Pro
- Customers Without a Completed Order Pro
Read mistakes 5, 6 and 11.
- Products Priced Above the Catalog Average Pro
- Employees Paid Above Their Department Average free
- Users With No Active Subscription Pro
Read mistake 2.
- Completed-order rate per customer Pro
- Headcount by salary band Pro
- Category Share of Completed Revenue Pro
- Every employee and their manager (or none) Pro
Read mistake 7.
Checkpoint: take a mock interview and compare it with the previous ones. Redo the two problems above, then update your mistake log.
Week 3: dates, windows and analytics
- Latest Completed Order per Customer free
- Ranking Customers by Completed Orders: RANK vs DENSE_RANK Pro
- Top of the Pay Range in Each Department Pro
Read mistake 8.
- Top 2 Products by Revenue in Each Category Pro
- Running Total of New Subscriptions by Month Pro
- Salary Gap to the Next-Higher-Paid Colleague Pro
Read mistake 12.
Checkpoint: take a mock interview with the hardest difficulty you can manage (Senior needs Pro), then ask the tutor for feedback. Redo the two problems above.
Week 4: Python + databases, hard SQL, mocks
- Atomic Money Transfer Py free
- Idempotent Payment Recording Py Pro
- Batch Import with Savepoints Py Pro
- Safe Product Search Py free
- All-or-Nothing Customer Import Py Pro
- Keyset Pagination for Orders Py Pro
- Index the Hot Queries Py free
- Fix the N+1 Customer Report Py Pro
- Employees as Dictionaries Py Pro
Checkpoint: take two mock interviews (SQL and a mixed one) and get the tutor's feedback on both. Pick your three weakest topics from the mistake log and fix them. The two problems above are stretch work.
Warm up with the two problems above, re-read the checklist and the framework, and stop early. Rest is part of the plan.
Day-before and day-of checklist
The day before
- Confirm the format, the time, the tools and the dialect with the recruiter.
- Test your setup: link, camera, microphone, internet, a charged laptop, a second way to connect.
- Re-read your mistake log and the thirteen classic mistakes above.
- Re-read the five-step framework and say it aloud once.
- Do one or two easy problems to warm up. Do not start a marathon.
- Refresh the syntax you always forget: window frames, date functions for the dialect, string functions.
- Prepare two or three questions for the interviewer, and a short story about a data problem you solved.
- Sleep. A rested brain beats a cram session.
The day of
- Eat, drink water, and have paper and a pen ready for sketching tables.
- Warm up for 15 minutes with an easy query so your hands remember the syntax.
- Start each question with clarifying questions, then write the output columns before any SELECT.
- Build in steps, narrate, and test with a small example before you say “done”.
- If you are stuck for more than a couple of minutes, say so and shrink the problem.
- Keep an eye on time: get a correct basic answer first, then improve it.
- Leave a few minutes to re-read the question and check your result against it.
- At the end, ask your questions and what the next steps are.
Practising with the AI tutor
The QueryGym tutor works best as a patient mentor and as a mock interviewer. It sees the problem, your code and the check results, so you do not have to paste anything. It can answer in English or Russian, which is handy if you will be interviewed in a language other than the one you study in. You need to be signed in to use it, and there is a limit of 20 messages per day, so make each question count.
Ask for hints, not answers
- Try first. Spend 10 to 15 minutes alone, then ask.
- Be specific: “Why does this return 12 rows when I expect 9?” is better than “help”.
- Ask for the smallest nudge: “Give me a hint without the query” or “What should I check first?”. Hints are graded, so ask for the next one only when you need it.
- Use the check button to verify, not the chat. When the check passes, ask “why does this work?” and explain it back.
The two interviewer actions
Interview me on this problem
The tutor plays the interviewer: it asks clarifying questions, asks about your approach, edge cases and complexity, and follows up. Answer in full sentences, as you would out loud.
Review it like an interviewer
After you solve a problem, ask for a critique in the way an interviewer would give it: correctness, edge cases you missed, readability, performance, and what they would probe next.
After a mock interview
When a mock interview ends, you get a report and can ask the tutor for feedback on the session: what went well, where you lost time or points, and what to practise next. Feed that into your mistake log and your next plan day.
The tutor can be wrong, and it is a study aid, not a promise of an offer. Trust the checker for correctness. If an explanation does not make sense, say so and ask again.
Ready to try it under the clock?
Run a free mock interview, then come back to the plan that fits your week.