Picking the right container
Choosing the wrong type is like storing soup in a colander. PostgreSQL has one of the richest type systems of any database — get the type right at table-creation time and a hundred future bugs disappear before they happen.
The 90% types
- TEXT for all string columns.
VARCHAR(n)only when you genuinely need a hard length limit. - INTEGER / BIGINT for whole numbers. Use BIGINT for any column that could ever exceed ~2.1 billion (foreign keys to large tables, IDs, byte counts).
- NUMERIC(p, s) for money and any value where rounding matters. Never use FLOAT/REAL for money — floating-point math will quietly destroy you.
- BOOLEAN for true/false. Don't use INTEGER 0/1 unless you have a very strong reason.
- TIMESTAMPTZ for any "when did this happen" column. Always with timezone — never bare TIMESTAMP.
- DATE for date-only data (birthdays, due dates, fiscal periods).
- UUID for public-facing identifiers;
uuidv7()in PG 18+ is time-sortable and index-friendly. - JSONB for variable-shape data; TEXT[] for tag lists; BYTEA for binary blobs.
The most common mistakes
Storing money as FLOAT. Storing timestamps as TEXT or as plain TIMESTAMP without timezone. Using VARCHAR(255) reflexively because some tutorial said to. Storing booleans as VARCHAR. Each of these is hours of debugging compounded across the lifetime of the codebase.