An index is a data structure that the database maintains alongside a table to enable fast lookup of specific rows without scanning the entire table. Without an index, finding all innings where a player scored more than 100 runs requires reading every row in an innings table of 10 million rows and evaluating the condition for each one — an O(n) sequential scan. With a B-tree index on runs_scored, the database traverses a balanced tree to find the qualifying range in O(log n) steps, then reads only the matching rows. Indexes are the primary tool for transforming unacceptably slow queries into acceptably fast ones.
PostgreSQL provides four primary index types, each optimised for a different access pattern. B-tree indexes support equality and range comparisons and are the default and most widely applicable type. Hash indexes support only equality comparisons and are faster for exact lookups than B-tree on very large tables. GIN (Generalised Inverted Index) indexes support full-text search, JSONB containment, and array element lookups. BRIN (Block Range Index) indexes are extremely compact and suitable for naturally ordered large tables like time-series data. Choosing the right index type is as important as deciding to index at all.
Indexes are not free — every index adds write overhead to INSERT, UPDATE, and DELETE operations because the index must be updated alongside the table. An index also consumes disk space (often 10–40% of the indexed column's data size). In write-heavy OLTP tables (high insert rate), too many indexes slow down ingestion without proportionally improving query performance. In read-heavy analytical tables, insufficient indexes cause slow queries. Index design is a deliberate trade-off between read performance, write performance, and storage — not a 'more is always better' decision.