The one thing to understand first
Every SQL statement goes through parse → analyze → rewrite → plan → execute. For repeated queries that differ only in parameter values, redoing parse and plan each time is wasted work. Prepared statements cache the parsed and (sometimes) planned form so subsequent executions skip that overhead. The logic lives in src/backend/utils/cache/plancache.c.
Plan caching is a bet that one plan fits all parameter values — and on skewed data that bet eventually loses. The classic failure is a query that is fast for the first five executions, then “warms up” into a generic plan and becomes pathologically slow for certain inputs.
Prepared statements
PREPARE getcust (int) AS
SELECT * FROM orders WHERE customer_id = $1;
EXECUTE getcust(42);
Most applications use the protocol-level equivalent automatically — drivers like JDBC and libpq issue Parse/Bind/Execute messages, creating server-side prepared statements without explicit PREPARE.