0Pricing
SQL Interview Prep · Lesson

Spotting and Fixing Slow Queries

A diagnostic checklist for the 'this query is slow, fix it' interview prompt.

Spotting and Fixing Slow Queries is a free SQL Interview Prep lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the SQL Interview Prep learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The 'This Query Is Slow, Fix It' Prompt

This is the capstone interview prompt: the interviewer hands you a slow query and an EXPLAIN ANALYZE plan and asks you to diagnose it. They are testing a method, not memorized tricks.

A strong answer follows a checklist out loud: measure, read the plan, find the dominant cost, form a hypothesis, propose a fix, and verify. This lesson builds that checklist step by step.

Stay systematic and narrate your reasoning, that is what earns the senior rating.

Step 1: Measure With EXPLAIN ANALYZE

Never guess from the SQL alone. Get the real plan with EXPLAIN (ANALYZE, BUFFERS).

ANALYZE gives actual times and row counts; BUFFERS shows whether you are hitting cache or reading from disk. Together they tell you if the query is CPU-bound, I/O-bound, or just doing too much work.

Run it a couple of times; the first run may pay a cold-cache penalty that distorts timing.

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at >= '2026-01-01';

Step 2: Find the Dominant Node

Do not read top-to-bottom hunting randomly. Find the node where the most time is actually spent.

Compute each node's self time: its total actual time minus the time of its children, multiplied by loops. The node with the biggest share is your target; everything else is noise.

In interviews say: 80 percent of the runtime is in this one Seq Scan, so that is where I focus. Optimizing anything else would be wasted effort.

Step 3: Check Estimated vs Actual

At the dominant node, compare estimated rows to actual rows. A large gap means the planner is flying blind and likely chose a bad plan (wrong join algorithm, wrong access method).

The example shows a 1000x underestimate. Before redesigning anything, refresh statistics, this single command often fixes the plan for free.

ANALYZE recomputes column statistics; VACUUM ANALYZE also cleans dead tuples and updates the visibility map.

-- estimate rows=100, actual rows=120000  -> stale stats
ANALYZE orders;
-- or, for bloated tables:
VACUUM ANALYZE orders;

Common Cause: Function on an Indexed Column

The most frequent fixable bug: a function or cast wraps the column in WHERE, so the index cannot be used and the engine seq-scans.

The example forces a full scan because DATE() is applied to every row. Rewrite it as a bare-column range predicate (sargable form), and the index on created_at kicks in.

Same idea for WHERE lower(email)=...: either store normalized data, query the bare column, or build an expression index.

-- Not sargable: index unusable
WHERE DATE(created_at) = '2026-01-01'

-- Sargable: range over the bare column
WHERE created_at >= '2026-01-01'
  AND created_at <  '2026-01-02'

Common Cause: Missing Index

If the dominant node is a Seq Scan with a highly selective filter, or a Nested Loop with huge loops on an unindexed inner key, the fix is usually an index.

Add an index on the filtered or joined column. The example creates one on customer_id so the join can switch from seq scans to index scans, and the planner may pick a far cheaper plan.

Verify by re-running EXPLAIN ANALYZE, do not assume the index helped.

CREATE INDEX idx_orders_customer
  ON orders (customer_id);

Common Cause: SELECT * and Wide Rows

SELECT * drags every column off disk and over the wire, and it prevents index-only scans because the index rarely covers all columns.

Select only the columns you need. This shrinks row width, lowers I/O, and can enable a covering index-only scan.

An interviewer who plants SELECT * wants you to notice it. Trimming the column list is often a quick, real win on wide tables.

-- Before
SELECT * FROM orders WHERE customer_id = 42;

-- After: only needed columns (may enable index-only scan)
SELECT order_id, amount FROM orders WHERE customer_id = 42;

Common Cause: Spilling to Disk

If a Sort or Hash node reports disk usage (Sort Method: external merge Disk: 25000kB or Batches: > 1), the operation overflowed work_mem and spilled.

Options: raise work_mem for the session, reduce how many rows reach the sort/hash (filter earlier), or add an index that supplies sorted order so no sort is needed at all.

This is a precise, senior-level diagnosis interviewers reward.

Sort  (actual rows=2000000 loops=1)
  Sort Key: o.amount
  Sort Method: external merge  Disk: 25000kB

Common Cause: Over-Fetching Rows

Watch for Rows Removed by Filter: 9500000. The query read ten million rows and threw away almost all of them, classic wasted work.

Fixes: add an index so the filter is applied during the access (not after), make the predicate more selective, or push filtering earlier in the query so fewer rows flow up the tree.

The principle: do the least work, filter as early and as cheaply as possible.

Seq Scan on events
  Filter: (event_type = 'purchase')
  Rows Removed by Filter: 9500000

The Diagnostic Checklist

Recite this in the interview and you cannot lose your way:

  • Measure with EXPLAIN (ANALYZE, BUFFERS).
  • Locate the node consuming the most time.
  • Compare estimated vs actual rows, fix stale stats first.
  • Check sargability, remove functions from filtered columns.
  • Index selective filters and join keys.
  • Trim columns, avoid SELECT *.
  • Watch disk spills and over-fetching.
  • Verify by re-running the plan.

Putting It Together

Walk a full example aloud. Plan shows a Seq Scan on a 50M-row orders table, filter customer_id = 42, Rows Removed by Filter near 50M, estimate roughly matching actual.

Diagnosis: selective filter, no index, dominant cost is the scan. Fix: CREATE INDEX ON orders(customer_id). Re-run: plan flips to an Index Scan, time drops from seconds to under a millisecond.

That measure-diagnose-fix-verify loop is the answer template for any slow-query prompt.

CREATE INDEX idx_orders_customer ON orders (customer_id);
EXPLAIN (ANALYZE, BUFFERS)
SELECT order_id, amount FROM orders WHERE customer_id = 42;

Quick Check

A query filters with WHERE YEAR(order_date) = 2026 and the plan shows a full Seq Scan despite an existing B-tree index on order_date. What is the best first fix?

Recap

You now have a repeatable method for slow-query prompts:

  • Always measure with EXPLAIN (ANALYZE, BUFFERS) and focus on the dominant node.
  • Fix stale statistics first when estimates and actuals diverge.
  • Make predicates sargable, add indexes for selective filters and join keys, and trim SELECT *.
  • Address disk spills and over-fetching, then verify the new plan.

Narrate the checklist, propose a concrete change, and re-run the plan to prove it, that is the senior answer.

Frequently asked questions

Is the “Spotting and Fixing Slow Queries” lesson free?

Yes — the full text of “Spotting and Fixing Slow Queries” is free to read here on the web, and the SQL Interview Prep course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the SQL Interview Prep course, upgrade to CoddyKit PRO.

What will I learn in “Spotting and Fixing Slow Queries”?

A diagnostic checklist for the 'this query is slow, fix it' interview prompt. You practise SQL Interview Prep with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start SQL Interview Prep?

No prior experience is required. SQL Interview Prep on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Spotting and Fixing Slow Queries” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this SQL Interview Prep lesson?

Yes. Every SQL Interview Prep lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Reading an EXPLAIN Plan
  2. Seq Scan vs Index Scan vs Index-Only
  3. Join Algorithms: Nested Loop, Hash, Merge
  4. Spotting and Fixing Slow Queries
← Back to SQL Interview Prep