Does the optimized column order for a PostgreSQL table always have variable length types at the end?
The comment from pg_column_byte_packer appears to be not wrong as text columns do have a pg_type.typalign of 4, but they've also got a pg_type.typlen of -1.

Had me puzzled, too, when I researched it a couple of years back. typlen = -1 just indicates varlena storage, which nominally has typalign = 'i' (integer alignment, needs to start at 4-bytes offset). But that's not the whole story. Eventually, I found the explanation in a note in the source code:

Note also that we allow the nominal alignment to be violated when storing &quot;packed&quot; varlenas; the TOAST mechanism takes care of hiding that from most code.

So a varlena datum &lt; 127 bytes (after possible compression) only adds 1 byte of overhead (signifying its length) and requires no alignment padding &quot;on disk&quot;. (Hardly any &quot;disks&quot; any more these days.)
See:

Calculating and saving space in PostgreSQL (where I coined the term &quot;Column Tetris&quot;)

To answer the question in the title:

Does the optimized column order for a PostgreSQL table always have variable length types at the end?

True-ish. Datums exceeding 127 bytes cannot be stored in optimized form and fall back to requiring &quot;integer alignment&quot;. If we don't know that a varlena-type column will stay below that threshold (most of the time), we can't say for sure.
Also, there are additional considerations for optimized storage. With many columns in a row, it's cheaper to compute tuple storage offsets with NOT NULL, fixed-size length columns first. Placing frequently accessed columns first also yields a tiny advantage. All of this is further complicated by the TOAST mechanism and index(-only) access.
But all of these effects are typically tiny. And losing 3 bytes to alignment padding pales in comparison when a column occupies 200 bytes. So mostly not worth the bother. The rule of thumb covers most of it:
Sort columns by their required alignment typalign:
d --&gt; i --&gt; s --&gt; c.
But typlen = -1 (&quot;varlena&quot;) last (typically), even though formally typalign = 'i'.
The manual:

c = char alignment, i.e., no alignment needed.
s = short alignment (2 bytes on most machines).
i = int alignment (4 bytes on most machines).
d = double alignment (8 bytes on many machines, but by no means all).


Your example

So in the case of a table that has an integer column, a text column, and a smallint column, pg_column_byte_packer will put the text columns right in between the two.

pg_column_byte_packer is living up to its name. int --&gt; text --&gt; smallint is as tight as it gets.
For the typical case of short strings, the only relevant decision is to put int first. smallint can at most force 1 additional byte of alignment padding at odd byte offsets. Since tuple space is always allocated in multiples of 8 bytes, this can never result in a bigger tuple.
Strings exceeding the threshold of 127 bytes on disk (including 1 leading length-byte), flip to 4 leading length bytes and require the nominal integer alignment. That's where putting text before smallint can safe 8 bytes effectively. With random string length that happens in 25 % of cases, so 2 bytes on average for tuples of at least 144 bytes.
There's that. But putting the smallint first typically has tiny advantages, and most text columns stay well below the length threshold.
The thing to remember is not to intersperse multiple smallint and text columns. Multiple offsets can add up in this case.