Heap Tables
standardintermediatePostgreSQL's default table storage is a heap: rows are stored in no particular guaranteed order, physically wherever there is room, rather than sorted by any key the way a clustered-index table in some other databases is. Finding a specific row by value therefore normally requires an index — without one, PostgreSQL must scan the heap looking at every row.
Think of it as
Think of a heap table as a big, unordered box of rows rather than a filing cabinet sorted by a key — a new row goes wherever there is free space (a page with room, or a new page at the end), not into a specific sorted position. This is exactly why an index exists as a separate structure: the heap itself makes no promises about order, so anything that needs fast lookup by value needs a separate, ordered structure (an index) pointing back into the heap.
What we're doing: Show that a heap table's physical row order does not match insertion or key order after some updates and deletes create gaps that later inserts reuse.
- 3
- Deleting row 2 frees physical space somewhere in the table.
- 5–6
- A later insert may reuse that freed space rather than appending strictly at the end.
- 10
- ctid exposes the actual physical location — comparing it against id order makes the lack of a physical ordering guarantee directly observable.
ctid | id | total
-------+----+-------
(0,1) | 1 | 100
(0,4) | 4 | 400
(0,3) | 3 | 300Why this works: The physical ctid values do not increase monotonically with id — row 4 physically landed before row 3 in this example, which is completely normal and expected for a heap: nothing about heap storage promises physical order matches any column's logical order, which is exactly why a query that needs rows "in order" relies on an index or an explicit ORDER BY, never on assumed physical layout.
Assuming rows come back in insertion or primary-key order without an explicit ORDER BY
Wrong
Better
What you see: A report or export that relied on unordered SELECT results "happening" to come back in a sensible order works fine in development, then produces visibly scrambled output in production once the table has seen enough deletes/updates to disturb its incidental physical layout.
Why: A heap table's physical storage order is an implementation detail with no guarantee attached to it — any code that depends on a particular row order must say so explicitly with ORDER BY, since the heap itself will happily return rows in whatever order is physically convenient, which can and does change as the table is written to over time.
Remember: A heap table stores rows in no guaranteed order — physical location depends on free space, not any key. Without an index, finding specific rows requires scanning the whole heap. Never assume a particular row order without an explicit ORDER BY, since the heap's physical layout is an implementation detail that can and does change.
See also: pages tuples and visibility information · why indexes trade storage and write cost for read speed

