A PostgreSQL B-tree leaf page has finite space for index tuples. When an incoming tuple belongs on a page that no longer has room, the access method must make space, and a page split can add another leaf page plus a new parent downlink. The fillfactor storage parameter controls how tightly leaf pages are packed at selected points in the index lifecycle, leaving capacity that later writes can consume.
For B-tree indexes, PostgreSQL uses a default fillfactor of 90. A value below 100 deliberately exchanges denser initial storage for free space on leaf pages. That space is not a permanent reservation for a particular row or key. It is simply unused page capacity available to later index activity.
Fillfactor acts on physical page occupancy
An index tuple stores key data and a reference to a table tuple, with representation details depending on the index definition and B-tree features in use. Leaf pages hold these tuples in key order. An index build can therefore pack a page until it reaches a target occupancy and then continue on another page.
A B-tree created with an explicit setting might look like this:
CREATE INDEX events_created_at_idx
ON events (created_at)
WITH (fillfactor = 75);During the initial build, PostgreSQL targets the configured percentage for leaf-page packing. The same parameter also applies when the B-tree extends at its right edge as new largest key values arrive. With fillfactor = 75, the intended effect is more free capacity than the default setting leaves at those points.
The parameter accepts integer values from 10 through 100 for B-tree indexes. Setting 100 asks the build to pack leaf pages completely. That can minimize initial index size for data that remains static, but it removes the planned free margin for subsequent index tuples.
Fillfactor does not impose a permanent maximum occupancy. A page that begins below full capacity can later consume its free space. Once it can no longer fit an incoming tuple, normal B-tree maintenance rules apply.
A split changes more than one leaf page
A page split creates a new page and redistributes items so the incoming tuple can be accommodated. The parent level also needs a downlink for the new page. If that parent cannot accept the additional downlink, the split can propagate upward. A root split adds another level to the tree.
Free space can postpone that event when later tuples target a page that still has capacity. This is the main physical connection between B-tree fillfactor and page splits: lower initial occupancy provides room that some future writes can use without immediately requiring another leaf page.
The effect depends on the key distribution and write pattern. Reserved capacity on one leaf page does not help an insertion that belongs on a different full page. A workload that concentrates writes in particular key ranges can consume free space unevenly. The configured percentage therefore does not translate into a fixed split reduction.
PostgreSQL documentation describes lower B-tree fillfactor values, particularly in the 50 to 90 range, as potentially smoothing page-split rates during the early life of indexes that expect many inserts or updates. It also notes that any reduction in the absolute split count is workload dependent.
Right-edge growth has distinct behavior
Monotonically increasing keys create a common B-tree pattern. An index on an increasing sequence or timestamp often receives new entries at the right edge because each new key sorts after existing keys.
PostgreSQL applies B-tree fillfactor when extending that right edge. A newly established rightmost leaf page is not necessarily packed to 100 percent merely because inserts arrive in key order. The configured target leaves room as the index continues to receive larger keys.
This detail separates fillfactor from a one-time build option. Its influence can continue during right-edge extension, even though pages elsewhere in the tree may have evolved far beyond their occupancy immediately after the original build.
The free margin still has a storage cost. More unused capacity means the same logical set of index entries can require more pages. Additional pages can increase the physical footprint of the index. A lower setting is therefore not a generic performance setting; it changes the space available to absorb later writes.
Updates can also create index pressure
An UPDATE can result in new physical index tuples even when an indexed value appears logically unchanged. PostgreSQL’s MVCC representation can create successive table tuple versions, and B-tree maintenance includes mechanisms such as bottom-up deletion to control obsolete version churn.
B-tree bottom-up deletion can benefit from some extra page space because successor index tuples may need to coexist temporarily with older versions. PostgreSQL documents a connection between this mechanism and fillfactor, while also noting that the effect is usually not significant.
Other mechanisms can change page pressure as well. B-tree deduplication may represent equal keys with posting-list tuples, reducing repeated key storage when the index is eligible. Opportunistic deletion can reclaim tuples marked dead. These behaviors mean that page occupancy is not determined by fillfactor alone.
The useful interpretation is narrow: fillfactor sets a packing target in defined B-tree operations. It does not disable splitting, guarantee a particular amount of free space over time, or replace index cleanup.
Full packing fits a genuinely static index
A fillfactor of 100 has a clear physical result during index construction: leaf pages can be packed without the usual free margin. For an index that will receive no inserts or updates, that can reduce space left unused solely for future writes.
The condition matters. PostgreSQL cautions against 100 for indexes that will later receive changes because even a small amount of write activity can encounter pages with no planned capacity and trigger a burst of splits.
At the other end, choosing a very low value without a write pattern that can use the reserved capacity can leave substantial space idle. The setting belongs to the index’s expected mutation pattern rather than to a universal tuning rule.
REINDEX creates a fresh physical index and applies the index’s storage parameters during that build. That makes fillfactor part of the resulting page layout again. Changing a storage parameter on an existing index does not retroactively rearrange every existing leaf page into the new target occupancy; a physical rebuild is the operation that reconstructs the index layout.
B-tree fillfactor is best treated as control over initial and right-edge page packing, with later page state determined by actual writes and maintenance. The useful boundary is physical: it can provide room before a split becomes necessary, but it cannot dictate where future keys land or how long that room remains available.