Database·August 18, 2026

The Postgres indexing mistake that quietly kills API latency

Postgres doesn't index foreign keys automatically. Learn how to spot a missing index, build a composite index that matches your query, and avoid over-indexing.

An endpoint that feels instant with a hundred test rows can turn into a multi-second request once a Postgres table holds real data. The cause is almost always the same: a missing index. This guide explains why that happens and how to find the one you're missing.

The fix is usually cheap, often a single index, but only once you know which one and why it was missing. Guessing wastes time. A short diagnostic step points straight at the cause.

Key takeaways

  • Postgres indexes primary keys and unique constraints automatically, but not foreign key columns.
  • A missing index on a foreign key forces a full table scan on every join or filter that uses it.
  • A query that filters and sorts on two columns needs one composite index in the right order, not two separate indexes.
  • EXPLAIN ANALYZE shows you the real cause in about two minutes, so you don't have to guess.

What is a database index?

An index is when the database keeps a separate, sorted structure for a column, so it can jump straight to matching rows instead of reading the whole table. Without one, Postgres checks every row in turn, called a sequential scan. That's fine on a small table and slow on a large one.

Why foreign keys don't get an index automatically

Postgres automatically creates an index for a primary key and for any column with a unique constraint. It doesn't do the same for a plain foreign key column. For example, an order.customer_id column that references the customers table gets no index unless you add one yourself.

This matters because every join or filter on that column runs a sequential scan by default. On a table with a few hundred rows, that's invisible. On a table with a few hundred thousand rows, it can become the entire latency budget of the request.

Composite indexes need to match the query

A single-column index doesn't help every query. If a query filters on status and orders by created_at, it needs a composite index on (status, created_at), in that order. Two separate single-column indexes don't combine the way you might expect.

When the index doesn't match the query shape, the planner falls back to scanning far more rows than necessary, even if an index exists on each column individually.

How to find the missing index

Run the actual query through EXPLAIN ANALYZE. Look for a Seq Scan on a table with a meaningful row count, especially one sitting inside a nested loop. That's the real signal, not a guess about which endpoint feels slow.

We recommend making this a two-minute check before any data-heavy endpoint goes live. It's cheap, and it tells you exactly where to add the index instead of guessing.

The opposite mistake: too many indexes

Indexing isn't free. Adding an index for every column that appears in a WHERE clause speeds up reads but slows down writes and adds storage cost. Add indexes deliberately, based on the queries your application actually runs, not as a reflex applied to every column a query touches.

Common mistakes

  • Assuming foreign keys are indexed. Postgres only indexes primary keys and unique constraints automatically.
  • Adding single-column indexes for a multi-column query. A query that filters and sorts on two columns needs one composite index, not two separate ones.
  • Guessing which endpoint is slow. Run EXPLAIN ANALYZE instead of guessing from feel.
  • Indexing every column. Each index adds write cost and storage, so add them based on real query patterns.

FAQ

Does Postgres index foreign key columns automatically?

No. It indexes primary keys and unique constraints automatically, but a plain foreign key column needs its own index, added explicitly.

How do I know which index is missing?

Run the slow query through EXPLAIN ANALYZE and look for a Seq Scan on a table with a meaningful row count.

The bottom line

Most slow endpoints come down to the same cause: a missing or mismatched index, not a fundamentally slow database. Check whether your foreign keys are indexed, make sure composite indexes match how your queries actually filter and sort, and run EXPLAIN ANALYZE before guessing.

Database performance work like this is often one of the fastest fixes available to a slow product. The same connection and query patterns matter just as much once real traffic shows up.

Need help diagnosing a slow endpoint? Talk to our team.

Tell us what you're building.

Describe the problem in plain language. An engineer reads every message and replies within one business day.

Write to Us
Reply
Within one business day
First call
Free, no commitment
Confidentiality
NDA on request, before you share anything