Seq Scan vs Index Scan vs Index-Only
Why the planner chooses each and what it tells you about your query.
Seq Scan vs Index Scan vs Index-Only is a free SQL Interview Prep lesson on CoddyKit — lesson 2 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.
Three Ways to Read a Table
When the planner needs rows from a table it picks one of three access methods, and interviewers expect you to name all three:
- Seq Scan, read every row in the table from start to finish.
- Index Scan, walk an index to find matching rows, then fetch each from the table.
- Index-Only Scan, answer entirely from the index without touching the table at all.
Knowing why the planner chooses each is the core of this lesson and a guaranteed senior question.
What a Sequential Scan Does
A Seq Scan reads the table's pages one after another and applies any filter to each row. No index is consulted.
This sounds bad but it is often the right choice. Sequential reads are fast for the disk (no random jumps), so when a query returns a large fraction of the table, scanning everything beats jumping through an index millions of times.
The example: scan orders, keep rows where amount > 100. If most orders exceed 100, a seq scan is correct.
EXPLAIN SELECT * FROM orders WHERE amount > 100;
Seq Scan on orders (cost=0.00..18334.00 rows=900000 width=64)
Filter: (amount > 100)What an Index Scan Does
An Index Scan uses a B-tree to jump straight to matching keys, then reads the corresponding rows from the table heap.
It shines when the filter is selective, returning a small slice of the table. Looking up 5 rows via an index beats reading 10 million.
The plan names the index it used. Each match costs one index lookup plus one heap fetch (a random read), so index scans lose their edge once they return too many rows.
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;
Index Scan using idx_orders_customer on orders
(cost=0.42..38.50 rows=12 width=64)
Index Cond: (customer_id = 42)Selectivity Decides the Choice
The single concept that drives all of this is selectivity: what fraction of rows a predicate keeps.
- High selectivity (few rows match, like a unique id) favors an Index Scan.
- Low selectivity (many rows match, like
status IS NOT NULL) favors a Seq Scan.
A common rule of thumb: once a query returns more than roughly 5 to 10 percent of a table, the planner often prefers a sequential scan because the random heap fetches of an index become more expensive than reading everything in order.
The Index-Only Scan
An Index-Only Scan is the fastest of the three. If every column the query needs is already in the index, the engine never touches the table heap at all.
The example query selects only customer_id and filters on it, and the index is on customer_id. All needed data lives in the index, so Postgres reports Index Only Scan.
This avoids the random heap reads that slow an ordinary index scan, a huge win on wide tables.
EXPLAIN SELECT customer_id FROM orders WHERE customer_id = 42;
Index Only Scan using idx_orders_customer on orders
(cost=0.42..8.44 rows=12 width=4)
Index Cond: (customer_id = 42)The Visibility Map Catch
Interviewers love this nuance. An index-only scan still has to confirm each row is visible to your transaction (MVCC), and the index alone does not store visibility.
Postgres uses the visibility map: if a page is marked all-visible, it skips the heap; if not, it must fetch the heap row anyway. The plan shows Heap Fetches: N.
That is why a freshly updated table can show many heap fetches and slow index-only scans until VACUUM refreshes the visibility map.
Index Only Scan using idx_orders_customer on orders
(actual time=0.01..0.03 rows=12 loops=1)
Heap Fetches: 0Bitmap Scans: the Middle Ground
There is a fourth method that often appears: the Bitmap Heap Scan. The planner picks it when a predicate matches more rows than a plain index scan wants but fewer than a full table.
It first builds a bitmap of matching row locations from the index (Bitmap Index Scan), then fetches heap pages in physical order instead of random order. Ordered fetches are much cheaper than the scattered reads of a regular index scan.
Bitmap Heap Scan on orders (cost=12.0..520.0 rows=8000)
Recheck Cond: (status = 'pending')
-> Bitmap Index Scan on idx_orders_status
(cost=0..12 rows=8000)
Index Cond: (status = 'pending')Why the Planner Ignored Your Index
A classic interview prompt: I added an index but the plan still does a Seq Scan, why? Common reasons:
- The predicate is not selective, scanning is genuinely cheaper.
- A function wraps the column:
WHERE lower(email) = ...cannot use a plain index onemail. - Type mismatch forces an implicit cast that defeats the index.
- Stale statistics, run
ANALYZE. - The table is tiny, scanning a few pages beats index overhead.
Worked Diagnosis
Suppose orders has an index on created_at but this query still seq-scans:
The culprit is DATE(created_at). Wrapping the column in a function means the index on the raw created_at cannot be used. The fix is to rewrite as a range predicate that leaves the column bare, or build an expression index on DATE(created_at).
-- Slow: function on the indexed column
WHERE DATE(created_at) = '2026-01-01'
-- Fast: bare column, range uses the index
WHERE created_at >= '2026-01-01'
AND created_at < '2026-01-02'Comparing the Methods
Hold this comparison in your head for the interview:
- Seq Scan, best when returning a large fraction of rows; sequential I/O.
- Index Scan, best for selective lookups; index walk plus random heap fetches.
- Bitmap Heap Scan, mid-range match count; index to bitmap, then ordered heap reads.
- Index-Only Scan, fastest when the index covers every needed column and pages are all-visible.
The planner chooses by estimated cost, driven mainly by selectivity and statistics.
Forcing a Test (and Why Not in Prod)
To prove a point in development you can temporarily nudge the planner: SET enable_seqscan = off; forces it to prefer indexes so you can compare plans.
This is a diagnostic trick, never a production fix. Mention in interviews that the real solutions are better statistics, a suitable index, or rewriting the predicate, not disabling planner features globally.
SET enable_seqscan = off;
EXPLAIN ANALYZE SELECT * FROM orders WHERE amount > 100;
SET enable_seqscan = on;Quick Check
A query selects only email and filters on email, and there is a B-tree index on email. The plan shows Index Only Scan. Why is this faster than a regular Index Scan?
Recap
Key takeaways on access methods:
- Seq Scan wins for low-selectivity queries; Index Scan wins for selective ones.
- Index-Only Scan avoids the heap when the index covers all needed columns, watch
Heap Fetchesand the visibility map. - Bitmap Heap Scan bridges the middle by fetching heap pages in physical order.
- The planner decides by selectivity and statistics; functions on columns, type mismatches, and stale stats are why an index gets ignored.
Frequently asked questions
Is the “Seq Scan vs Index Scan vs Index-Only” lesson free?
Yes — the full text of “Seq Scan vs Index Scan vs Index-Only” 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 “Seq Scan vs Index Scan vs Index-Only”?
Why the planner chooses each and what it tells you about your query. 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 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Seq Scan vs Index Scan vs Index-Only” 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
- Reading an EXPLAIN Plan
- Seq Scan vs Index Scan vs Index-Only
- Join Algorithms: Nested Loop, Hash, Merge
- Spotting and Fixing Slow Queries