What techniques do you use to optimize database queries?
Query optimization is the discipline of reducing query latency and server load without changing correctness. It spans schema design, index strategy, query rewriting, and infrastructure choices.
**EXPLAIN / query plan analysis** is always the first step. Before optimising, measure. `EXPLAIN ANALYZE` (Postgres) or `EXPLAIN` (MySQL) shows whether the planner is performing a sequential scan, index scan, or nested loop join, and at what row estimate. Optimizing without looking at the query plan is guesswork.
**Indexing** is the highest-leverage change. Create indexes on columns used in `WHERE`, `JOIN ON`, and `ORDER BY` clauses. Composite indexes must match the query's column order—a composite index on `(user_id, created_at)` supports `WHERE user_id = ? ORDER BY created_at` but not `WHERE created_at = ?` alone. Partial indexes (Postgres) index only rows matching a condition, reducing index size for sparse queries. Covering indexes include all projected columns in the index itself, eliminating the heap fetch entirely.
**Avoiding N+1 queries**: N+1 occurs when code issues one query to fetch N parent records and then N individual queries for child records. Fix with JOIN or ORM eager loading (`SELECT IN` batch). Tools like Django Debug Toolbar or SQLAlchemy's echo mode surface N+1 in development.
**Query rewriting**: Rewrite correlated subqueries as JOINs or CTEs. Avoid `SELECT *`—projecting only needed columns reduces I/O and network transfer. Avoid functions on indexed columns in WHERE clauses (`WHERE YEAR(created_at) = 2024` prevents index use; `WHERE created_at >= '2024-01-01'` uses it).
**Pagination**: Offset pagination on large tables is expensive—the database must scan and discard all preceding rows. Keyset (cursor) pagination (`WHERE id > last_seen_id LIMIT 20`) is O(1) per page.
**Partitioning and sharding**: For very large tables, range or list partitioning prunes partitions at query time, reducing scanned data. Sharding distributes data across nodes but increases query complexity.
**Connection pooling**: Avoid opening a new database connection per request—connection setup is expensive. Use PgBouncer (Postgres) or application-level pools (SQLAlchemy pool). Pool exhaustion is a common performance cliff in high-concurrency applications.
Covers EXPLAIN/query plan analysis, indexing strategy (composite, covering), and N+1 elimination with concrete examples.
Covers all of the above plus keyset pagination, avoiding functions on indexed columns, connection pooling, and partitioning, with trade-offs for each.
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