Skip to content
spot_the_ai_bug

SQL interview questions: spot the bug

SQL bugs rarely throw errors. They just return the wrong rows, or the right rows very slowly. These interview questions cover NULL logic, date-range boundaries, injection in stored functions and index-killing patterns. Find the broken line, then compare your answer.

SQL gotchas interviewers love

  • Nothing equals NULL; use IS NULL / IS NOT NULL.
  • BETWEEN on timestamps silently drops the last day; use half-open ranges.
  • A leading % in LIKE can't use a B-tree index.
  • Dynamic SQL needs bound parameters too (EXECUTE ... USING).
  • Top-N queries need ORDER BY ... DESC.

5 SQL spot-the-bug questions

Each snippet has exactly one bug. Find the line before you open the answer.

Q1Off-by-onemedium

January revenue with BETWEEN

This should total all orders placed in January. Finance says the number is too low. Why? (created_at is a TIMESTAMP.)

SELECT SUM(total_cents) AS january_revenueFROM ordersWHERE created_at BETWEEN '2025-01-01' AND '2025-01-31';
Show answer

line 3 · Off-by-one

'2025-01-31' means midnight at the start of Jan 31, and BETWEEN is inclusive of exactly that instant. Every order placed later on Jan 31 is excluded.

Fix
WHERE created_at >= '2025-01-01'  AND created_at <  '2025-02-01';

Interview follow-up: Half-open ranges (>= start, < next start) work for every precision and avoid end-of-month arithmetic.

Dynamic SQL in a stored function

This PL/pgSQL function backs an admin search box. What's the vulnerability?

CREATE FUNCTION search_customers(p_name TEXT)RETURNS SETOF customers AS $$BEGIN    RETURN QUERY EXECUTE        'SELECT * FROM customers WHERE name ILIKE ''%' || p_name || '%''';END;$$ LANGUAGE plpgsql;
Show answer

line 5 · Security / injection

The parameter is concatenated into a dynamically executed string, so a name containing a quote can rewrite the query. Code inside the database is just as injectable as application code.

Fix
    RETURN QUERY EXECUTE        'SELECT * FROM customers WHERE name ILIKE $1'        USING '%' || p_name || '%';

Interview follow-up: Here the query doesn't need to be dynamic at all; a plain RETURN QUERY SELECT with the parameter is simplest.

Filtering with = NULL

This should list users who haven't verified their email (verified_at is NULL). Why does it always return zero rows?

SELECT id, emailFROM usersWHERE verified_at = NULLORDER BY created_at;
Show answer

line 3 · Null / undefined handling

Any comparison with NULL using = yields NULL (unknown), never TRUE, so no row passes the filter. NULL checks need IS NULL.

Fix
WHERE verified_at IS NULL

Interview follow-up: Related traps: NOT IN with a NULL in the list, and COUNT(column) skipping NULLs.

Leaderboard top 10

This query feeds the "Top 10 players" leaderboard. Why is the leaderboard full of beginners?

SELECT player_id, MAX(score) AS best_scoreFROM game_resultsGROUP BY player_idORDER BY best_score ASCLIMIT 10;
Show answer

line 4 · Logic inverted

ASC sorts from lowest to highest, so LIMIT 10 keeps the ten worst scores. A top-N query needs descending order.

Fix
ORDER BY best_score DESC

Interview follow-up: Also discuss ties: should players with equal scores share a rank (RANK() vs ROW_NUMBER())?

Leading-wildcard LIKE

users has 30M rows and a B-tree index on email. This query takes 20 seconds. Why?

SELECT COUNT(*)FROM usersWHERE email LIKE '%@gmail.com';
Show answer

line 3 · Performance trap

A B-tree index is ordered by the start of the value, so a pattern that starts with % can't use it. The database scans all 30M rows.

Fix
-- Store the domain separately (or use a generated column) and index it:ALTER TABLE users ADD COLUMN email_domain TEXT    GENERATED ALWAYS AS (split_part(email, '@', 2)) STORED;CREATE INDEX ON users (email_domain); SELECT COUNT(*) FROM users WHERE email_domain = 'gmail.com';

Interview follow-up: Alternatives worth naming: an index on reverse(email), or a trigram (pg_trgm) index for general substring search.

Every day, everyone gets the same pull request written by AI. It looks clean. It has comments. It has exactly one real bug.

  1. 1

    Find the line

    Tap the buggy line, then hit Submit. You get 3 attempts.

  2. 2

    Read the hints

    A miss within 2 lines is 🔥 Warm. Anything further is ❄️ Cold.

  3. 3

    Name the bug

    Found it? Bonus round: pick the bug type from 4 options. It doesn't affect your win, just your bragging rights.

// your share card

found in 3/3

🟥 cold miss · 🟨 warm miss · 🟩 found · ⬛ unused

Keyboard: ↑ ↓ to move between lines, Enter to submit. A new bug drops every day at midnight IST.

0
Played
0
Win %
0
Streak
0
Max

Attempts distribution