---
title: "Query Plan"
slug: "query-plan"
category: "glossary"
tags: ["glossary", "database", "postgres", "query-plan", "performance", "sql"]
status: "stable"
last_updated: 2026-08-14
summary: "A query plan is the execution strategy the database planner selects, describing which indexes, joins, and scan methods return rows for a SQL statement."
related:
  [
    "[[glossary/btree-index]]",
    "[[glossary/sequential-scan]]",
    "[[glossary/gin-index]]",
    "[[backend/postgres]]",
    "[[cheatsheets/postgres-explain]]",
    "[[glossary/vacuum]]",
  ]
---

## Overview

This page is the atomic definition. Reading and interpreting EXPLAIN output lives at [[cheatsheets/postgres-explain]] and [[backend/postgres]].

## Definition

A query plan is the tree of operations the database engine will execute to satisfy a SQL statement. In PostgreSQL, `EXPLAIN` shows the estimated plan; `EXPLAIN ANALYZE` runs the query and shows actual row counts, timing, and loop iterations. Each node in the tree is a plan node: `Seq Scan`, `Index Scan`, `Index Only Scan`, `Hash Join`, `Nested Loop`, `Sort`, `Aggregate`, etc.

The query planner chooses a plan by estimating the cost of each candidate strategy using table statistics (collected by `ANALYZE`). Cost estimates have two components: startup cost (work done before the first row is returned) and total cost. The planner picks the plan with the lowest estimated total cost.

Key plan nodes:
- `Seq Scan`: reads every row; cheap for small tables or low-selectivity queries.
- `Index Scan`: follows the index, then heap-fetches each matching row.
- `Index Only Scan`: reads from the index without heap access; requires a covering index and up-to-date visibility map.
- `Bitmap Heap Scan`: combines multiple index scans, then fetches heap pages in physical order.
- `Hash Join` / `Merge Join` / `Nested Loop`: three join strategies with different trade-offs on row count and sort order.

## When it applies

Run `EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)` on any slow query before adding an index. Check estimated vs. actual row counts: a large discrepancy means stale statistics; run `ANALYZE tablename`. Check `Buffers: shared hit` vs. `shared read`; high `read` means the working set does not fit in `shared_buffers`.

## Example

```sql
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, title FROM articles
WHERE author_id = 42
  AND published_at > now() - interval '30 days'
ORDER BY published_at DESC;
```

If the plan shows `Seq Scan` on a large `articles` table, consider a composite index on `(author_id, published_at DESC)`. After adding the index, re-run `EXPLAIN ANALYZE` to confirm `Index Scan` is chosen.

## Related concepts

- [[glossary/btree-index]] - the most common index type; the planner prefers it for equality and range queries.
- [[glossary/sequential-scan]] - the fallback scan method when no index is useful.
- [[glossary/gin-index]] - index for full-text and JSONB containment queries.
- [[backend/postgres]] - the Postgres deep-dive.
- [[cheatsheets/postgres-explain]] - the EXPLAIN cheatsheet with node annotations.

## Citing this term

> See [[glossary/query-plan|Query Plan]] (llmbestpractices.com/glossary/query-plan).

## Related

- [[glossary/btree-index]]
- [[glossary/sequential-scan]]
- [[glossary/gin-index]]
- [[backend/postgres]]
- [[cheatsheets/postgres-explain]]
