What is a covering index?
A covering index is an index that contains every column referenced in a query — in the SELECT list, WHERE clause, ORDER BY, and JOIN conditions — so the database engine can satisfy the entire query by reading the index alone, without accessing the underlying table (heap) pages. This access pattern is called an **index-only scan**.
To understand why this matters, consider how a standard B-tree index works. The index stores the indexed columns plus the row's physical location (heap pointer). For a query like `SELECT name, email FROM users WHERE email = 'x@y.com'`, if only `email` is indexed, the engine finds the matching rows in the index, then follows heap pointers to fetch `name` from the table pages — two I/O operations per row.
If you create `CREATE INDEX idx ON users(email, name)`, the index now contains both `email` and `name`. The engine finds the matching `email` entry in the index and reads `name` directly from the index leaf node — zero heap I/O. For high-cardinality filtered queries returning many rows, this difference is dramatic.
**When to use covering indexes:** - High-frequency read queries with stable column sets (dashboards, hot API endpoints) - Queries on large tables where heap I/O is the bottleneck - Sorting: if the covering index matches the ORDER BY, you avoid a sort step - COUNT/aggregate queries over filtered sets
**Trade-offs:** - Write overhead: every INSERT, UPDATE, or DELETE must update the index as well as the heap. Wider indexes mean more index maintenance. - Storage: covering indexes can be large — you're duplicating data into the index - Column order matters: the leftmost columns must match the WHERE clause for the index to be used at all. The covering columns (SELECT list) typically go at the end.
In PostgreSQL, `EXPLAIN (ANALYZE, BUFFERS)` shows `Index Only Scan` and `Heap Fetches = 0` when a covering index is fully effective. In MySQL InnoDB, secondary indexes already include the primary key, so covering is simpler to achieve — include all needed non-PK columns.
A practical pattern: identify your slowest N queries, check their execution plans for `Seq Scan` or `Heap Fetches > 0`, and add covering indexes targeting the specific column set of each hot query.
Correctly defines covering index as allowing index-only scans, explains the heap-access vs. no-heap-access difference, and names write overhead as the key trade-off.
Explains B-tree index structure and heap pointers, gives a concrete before/after CREATE INDEX example, covers column ordering rules (filter columns first), mentions EXPLAIN output to verify, and quantifies write trade-offs.
Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.
Get Graded — Free Assessment