When you use prepared statements in PostgreSQL, the database does not simply run your query the same way every time. Behind the scenes, PostgreSQL makes a continuous decision about how to plan and execute each statement depending on the parameters you pass. This decision revolves around two concepts: generic plans and custom plans. Understanding how PostgreSQL chooses between them can help you write faster queries, diagnose performance problems, and make better use of prepared statements in your applications.
A prepared statement is a query that you compile once and execute many times with different parameter values. In PostgreSQL, this is done using the PREPARE and EXECUTE commands, or automatically by client libraries that use the extended query protocol. The advantage of prepared statements is that they avoid repeating the parsing and analysis step on every execution. However, the planning step — where PostgreSQL decides how to physically execute the query — introduces a more nuanced tradeoff.
A generic plan is built without knowing the actual parameter values. PostgreSQL plans the query using placeholders and produces an execution strategy that is acceptable for any possible input. Because the planner does not know whether the parameter value will match one row or a million rows, it must make conservative assumptions. The resulting plan is stored in memory and reused across multiple executions, which saves the cost of replanning every time. The downside is that a plan built without actual values may not be optimal for specific inputs, especially when the data distribution is highly skewed.
A custom plan, on the other hand, is built with the real parameter values supplied by the caller. The planner treats these values as constants during optimization, which means it can accurately estimate selectivity, choose better indexes, and pick more appropriate join strategies. If your parameter matches a rare value that affects only a small number of rows, the planner can choose an index scan. If it matches a common value that covers most of the table, the planner might choose a sequential scan instead. Custom plans are more accurate but come with a cost: the planner must run from scratch every time, which adds CPU overhead to each execution.
PostgreSQL manages this tradeoff automatically through an adaptive algorithm. When a prepared statement is first executed, PostgreSQL always generates custom plans for the first five executions. This is the sampling phase. During this phase, PostgreSQL accumulates the cost of each custom plan, including the planning overhead. After five executions, it compares the average cost of the custom plans against the cost of a generic plan. If the generic plan is cheaper, PostgreSQL switches to it and reuses it from that point forward. If custom plans continue to be cheaper even when the planning cost is factored in, PostgreSQL keeps generating custom plans.
The cost comparison is subtle. When calculating the cost of a custom plan, PostgreSQL includes a planning surcharge to account for the work the planner did to build it. For a generic plan, this overhead is excluded because it was only paid once. This means the generic plan does not need to be execution-optimal to win — it just needs to be cheaper than repeatedly paying the cost of replanning plus executing a custom plan each time. In practice, this means queries with consistent data distributions will usually switch to generic plans relatively quickly, while queries on skewed data tend to stay on custom plans longer.
There are situations where the automatic algorithm does not get this right. If your data has extreme skew — for example, a status column where one value covers ninety percent of rows and another covers one percent — a generic plan might always choose the wrong execution strategy. In these cases, PostgreSQL provides the plan_cache_mode setting, which lets you override the decision. Setting it to force_custom_plan tells PostgreSQL to always replan using the actual parameter values, ensuring the planner always has accurate information. Setting it to force_generic_plan does the opposite, which is useful when replanning overhead is significant and you know the query plan does not vary meaningfully with the input.
You can observe this behavior in action using the pg_prepared_statements view, which shows how many times each prepared statement has been executed with a generic plan versus a custom plan. This makes it easy to identify statements that may have settled on the wrong strategy.
In summary, PostgreSQL's generic and custom plan mechanism is a cost-based adaptive system designed to balance planning overhead against execution accuracy. For most queries, the default automatic mode works well and requires no intervention. However, in high-throughput systems with skewed data or queries where the optimal plan changes significantly based on input values, understanding and controlling this behavior becomes an important tuning lever. Knowing when to trust the planner and when to override it is a key skill for any engineer working with PostgreSQL at scale.