One of the genuinely important partitioning parameters, and one you should understand rather than merely leave on — because it does its work at two different times, and the difference determines how much it can help your queries. Default on, context user, same family framing as enable_async_append: a diagnostic instrument, not a tuning knob. It’s the parameter that lets PostgreSQL skip scanning partitions that cannot possibly contain rows the query wants.
The point of pruning
A partitioned table is physically many tables, and a query against it becomes an Append over scans of the individual partitions. Without pruning, that Append scans every partition — a query for January’s data on a table partitioned by month reads all twelve months and throws eleven away. Partition pruning is the optimization that recognizes, from the query’s conditions on the partition key, which partitions can be eliminated entirely, so the plan touches only the partitions that might actually contain matching rows. On a table with hundreds of partitions and a query that wants one, this is the difference between a fast scan and a catastrophically slow one, and it is the single biggest reason partitioning speeds queries up at all.
A note on lineage: before PostgreSQL 11 this was done by constraint_exclusion, which compared query conditions against each partition’s CHECK-style bound constraints. Declarative partitioning replaced that with dedicated pruning logic that compares values directly against partition bounds — faster, and controlled by this parameter. For declarative partitions enable_partition_pruning is what matters; constraint_exclusion now applies mainly to legacy inheritance-based partitioning, and the two are separate switches with overlapping history that gets its own post.
Two times pruning happens, and two different EXPLAIN tells
This is the part worth internalizing, because it changes how you read a plan. Pruning occurs in two phases, and they leave different evidence.
Plan-time pruning happens when the partition key is compared against constants the planner can see — WHERE created = '2026-01-15'. The planner eliminates the non-matching partitions before execution even begins, so those partitions never appear in the plan at all. The tell is a line in plain EXPLAIN: Subplans Removed: N, telling you N partitions were pruned away and won’t be scanned. They’re simply gone from the plan.
Execution-time pruning, added in PostgreSQL 11, handles the harder case: values the planner couldn’t see because they aren’t known until the query runs. Three situations produce these — a generic prepared plan where the parameter is filled at EXECUTE time, a value coming from a subquery or InitPlan, and the parameter on the inner side of a parameterized nested loop join, which changes on every outer row. Here the pruned subplans do appear in the plan, because the planner had to keep them in case they were needed; the evidence lives in EXPLAIN (ANALYZE) instead. A partition pruned every time shows (never executed); a partition pruned on some iterations of a nested loop shows a loops= count lower than its siblings. This runtime pruning is what makes partitioning fast for parameterized and prepared queries — without it, a generic plan would scan every partition on every execution because the planner, by definition, didn’t know the value when it built the plan.
The practical upshot: to confirm pruning is working, you read two different places. Subplans Removed: in EXPLAIN for the plan-time kind; (never executed) and reduced loops= in EXPLAIN (ANALYZE) for the execution-time kind. A plan that looks like it’s scanning every partition under plain EXPLAIN may in fact be pruning most of them at runtime — you can’t tell without ANALYZE.
Symptoms that warrant flipping it
You reach for this switch when a query against a partitioned table is scanning partitions you’re confident it shouldn’t, and you want to confirm pruning is or isn’t the reason. Turn it off — SET enable_partition_pruning = off — re-run EXPLAIN (ANALYZE), and compare: with pruning disabled every partition is scanned, so the difference between that plan and the default one is exactly what pruning was buying you. If the default plan already prunes well (few partitions actually executed), pruning is working and your performance problem is elsewhere. If disabling pruning changes nothing — every partition was being scanned even with it on — then pruning wasn’t happening, and that’s the real finding.
When pruning isn’t happening on a query you expected it to, the cause is almost never this parameter, which is on by default; it’s that the query gave the planner nothing to prune with. The usual reasons: the WHERE clause doesn’t constrain the partition key (pruning can only use conditions on the column the table is partitioned by), or the condition is obscured by a function or a type mismatch the planner can’t match against the bounds, or the partition key is wrapped in an expression. The fix is to write the query so the partition key is compared cleanly against a value the planner or executor can use — not to touch this GUC.
As with the whole family, enable_partition_pruning = off is a probe, and a purely diagnostic one — there is essentially no production reason to disable it, since a plan that scans every partition when it could scan one is almost always catastrophically worse. Its only real use is exactly this comparison: turn it off to measure what pruning is doing for you, confirm the query is giving the planner a partition-key condition it can act on, and turn it back on. If pruning isn’t happening, fix the query, not the parameter.