The query planner does not directly know which index to use; it estimates the cost of every candidate plan based on statistics stored in pg_statistic (exposed via pg_stats) and picks the lowest cost, which sometimes means choosing a sequential scan over an available index. ANALYZE refreshes these statistics, both manually and during autovacuum after a configurable fraction of rows has changed. Stale statistics mislead the planner into bad cardinality estimates, bad join orders, and bad memory sizing. Per-column sampling depth can be raised with ALTER TABLE t ALTER COLUMN c SET STATISTICS 1000 followed by ANALYZE, and functional dependencies recorded in pg_stats help the planner when correlated columns appear in the same query.
EXPLAIN is the window into planner decisions. EXPLAIN (ANALYZE, BUFFERS) SELECT ... shows the actual plan with row counts, timings, and buffer usage, while EXPLAIN (FORMAT JSON) provides a machine-readable form for tools like pev2 and pg_plan_guarantee. The auto_explain contrib module can log slow-query plans automatically using log_min_duration, with sample_rate and log_nested_statements parameters to control verbosity. Among the plan nodes, Bitmap Index Scan builds a page-level bitmap that may combine (BitmapAnd, BitmapOr) results from several indexes; Bitmap Heap Scan then visits those pages in physical order, applying a Recheck Cond when work_mem forces the bitmap to degrade to a lossy one-bit-per-page representation. Increasing work_mem keeps more bitmaps exact and reduces rechecks.
Three cost parameters strongly influence index selection. random_page_cost estimates the cost of an index scan (lowered to around 1.1 on SSD storage to favor indexes); seq_page_cost estimates sequential heap page reads (defaulting to 1.0); and effective_cache_size hints how much of the database fits in OS and shared buffer caches, nudging the planner toward index scans when the hint is large. Joining strategies also rely on indexes in different ways: Nested Loop probes an inner index for each outer row, Merge Join benefits when B-tree indexes already provide sorted inputs, and Hash Join builds its own hash table and does not depend on indexes. Disabling plan types with enable_indexscan = off (or similar enable_* GUCs) is acceptable for diagnosis but should never reach production configurations.