{"post":{"slug":"what-is-a-composite-index-2026","title":"What is a composite index? The 2026 guide to column order","excerpt":"A composite index covers more than one column, and the order of those columns changes which queries it can actually help. Here is how to get it right.","category":"code","date":"July 17, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">C</span>omposite indexes cover more than one column in a single index structure, sorted by the leading column, then by the next column within each group, and so on down the list. The mechanics are simple once you see them; the part that trips people up is that column order is not cosmetic. The same three columns, indexed in a different order, can turn a fast query into a sequential scan with nothing else changing.\n\n## What changed in 2026\n\n- **Index skip scan support arrived in more engines.** Postgres 18 and recent MySQL versions can now use a composite index even when a query skips its leading column, in some cases — a limited exception to the traditional left-prefix rule, not a replacement for good column ordering.\n- **Redundant-index detection tools got better.** `pg_stat_user_indexes` dashboards in managed providers now flag single-column indexes made obsolete by an existing composite index automatically.\n- **More query plan visualizers highlight index-column usage directly** — showing exactly which columns of a composite index a given plan actually used, rather than just \"index used: yes.\"\n\n## What a composite index actually stores\n\nThink of a composite index on `(status, created_at)` as a sorted list: every row grouped by `status` first, and within each `status` group, sorted by `created_at`. That structure directly serves a query filtering on `status` alone, and one filtering on `status` plus ordering or range-filtering by `created_at`, since within any one status the rows are already in `created_at` order. It does not serve a query filtering on `created_at` alone, since the index is not sorted by `created_at` globally, only within each status group.\n\n## The column order rule\n\nThe working rule: equality-filtered columns first, in roughly descending order of selectivity, then the one column you range-filter or sort by last.\n\n```sql\n-- Query: WHERE hotel_id = 42 AND status = 'confirmed' AND check_in_date > '2026-08-01'\n-- Correct order: both equality columns first, range column last\nCREATE INDEX idx_bookings_hotel_status_checkin\n  ON bookings (hotel_id, status, check_in_date);\n\n-- Wrong order for the same query — range column placed too early\nCREATE INDEX idx_bookings_wrong\n  ON bookings (check_in_date, hotel_id, status);\n-- This can only use check_in_date as a range scan; hotel_id and status\n-- cannot narrow the scan the way they could in the first version.\n```\n\nThe columns after the first range predicate in the index stop contributing to narrowing the scan — they can still be checked, but only row by row, not through the sorted structure.\n\n## A worked example\n\nSuppose `bookings` filters most often by `hotel_id` and `status`, then sorts by `check_in_date`:\n\n| Index | Query it fully serves | Query it partially serves |\n| --- | --- | --- |\n| `(hotel_id, status, check_in_date)` | `WHERE hotel_id = ? AND status = ?` sorted by `check_in_date` | `WHERE hotel_id = ?` alone (uses only the leading column) |\n| `(status, hotel_id, check_in_date)` | Same query, different column written first — equally valid if `status` is more selective | Queries filtering only by `hotel_id` (skips the leading column) |\n| `(check_in_date, hotel_id, status)` | Range queries on `check_in_date` alone | Nothing that also needs to filter tightly by `hotel_id` first |\n\nThe first two rows are both defensible depending on which column eliminates more rows on your actual data; the third is very likely wrong for this workload.\n\n## When a composite index replaces several single-column indexes\n\nA single composite index on `(hotel_id, status, check_in_date)` already serves any query that filters on `hotel_id` alone, since that is the leading column — a separate single-column index on just `hotel_id` is redundant and only adds write cost. This is one of the most common wins available: audit `pg_stat_user_indexes` for indexes with a leading column duplicated by a wider composite index, and drop them. It shrinks the [query optimization](/blog/what-is-query-optimization-2026) surface the planner has to reason about, cutting write overhead with no read-side downside.\n\n## Common mistakes\n\n**Ordering columns by how they appear in SELECT, not WHERE.** Column order should be driven entirely by how the query filters and sorts, not by any other convention.\n\n**One composite index per query, without checking for overlap.** Before adding a fourth narrow composite index, check whether an existing one already covers the new query as a left-prefix.\n\n**Forgetting that write cost scales with every index.** A composite index still costs a write on every insert and update, same as a single-column one — consolidating three indexes into one composite index is a genuine improvement, not a free additional index.\n\n## FAQ\n\n**Does column order in a composite index matter for ORDER BY too?**\nYes — put the sort column last, after your equality-filter columns; matching sort direction (ascending or descending) can avoid an extra sort step.\n\n**How many columns should a composite index have?**\nThere is no fixed limit, but each extra column adds diminishing returns and more write overhead. Three or four columns covering your most common filter pattern is typical; wider than that usually signals the query needs rethinking.\n\n**Is a composite index the same as a covering index?**\nNo, though they can overlap. A composite index is about which columns form the sortable key; a [covering index](/blog/what-is-a-covering-index-2026) is about including enough columns that the query never fetches the full row.\n\n## Where to go next\n\n- [What is a covering index in 2026](/blog/what-is-a-covering-index-2026)\n- [What is query optimization in 2026](/blog/what-is-query-optimization-2026)\n- [What is a database deadlock in 2026](/blog/what-is-a-database-deadlock-2026)","hue":200,"metaTitle":"What Is a Composite Index? 2026 Column Order Guide","metaDescription":"What is a composite index in 2026? How column order determines which queries benefit, worked SQL examples, and when it replaces several single-column indexes.","keywords":["what is a composite index 2026","composite index explained","composite index column order","multi column index sql","left prefix rule index","composite index vs single column index","postgres composite index example","index column order equality range","when to use composite index"],"tldr":["**A composite index is one index built across multiple columns**, sorted by the first column, then the second within it, and so on.","**Column order determines which queries benefit** — a query must use the indexed columns left to right, without skipping any, to get the full win.","**Equality columns belong before range columns** — put columns you filter with an exact match first, and the one column you filter with a range last.","**One well-ordered composite index often replaces three single-column indexes**, cutting write overhead without losing read performance.","**Skip:** creating a separate single-column index on a column that is already the leading column of a composite index — it is redundant."],"readTime":"4 min read","wordCount":900},"related":[{"slug":"what-is-a-covering-index-2026","title":"What is a covering index? The 2026 guide to index-only scans","excerpt":"A covering index holds every column a query needs, so the database never has to fetch the underlying row. Here is how that shortcut actually works.","category":"code","date":"July 17, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":240},{"slug":"indexes-explained-2026","title":"Database indexes explained in 2026: what every developer must know","excerpt":"Indexes make or break query performance. Here is the 2026 guide covering B-tree, GIN, GiST, and partial indexes with concrete examples and tradeoffs.","category":"code","date":"April 6, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":309},{"slug":"database-indexing-explained-2026","title":"Database indexing explained in 2026: what every developer must know","excerpt":"Bad indexes cause more production incidents than almost any other root cause. Here is the 2026 guide to choosing, building, and maintaining the right ones.","category":"code","date":"March 10, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":318}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"what-is-a-color-e-reader-2026","title":"What Is a Color E-Reader in 2026: How It Works","excerpt":"A color e-reader uses a filter layer over standard E-Ink to add color without a backlight. Here is how the technology works in 2026, and where it still falls short of a tablet screen.","category":"tech","date":"July 17, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":305},"older":{"slug":"what-is-a-cosigner-risk-2026","title":"Cosigner risk in 2026: what you are actually agreeing to","excerpt":"Cosigning a loan means you owe the money if the borrower does not pay, full stop. Here is what changes your credit, your liability, and your options in 2026.","category":"finance","date":"July 17, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":230}}