Normalization vs denormalization
Normalization removes redundancy so each fact lives in one place; denormalization duplicates it to avoid joins. The tradeoff is writes-and-integrity vs reads-and-speed.
Normalization is organizing a database so that each fact is stored exactly once. Every piece of information has a single home, and everything else points at it by key. Denormalization is the deliberate opposite: copying data into more than one place so a query can read it without chasing joins. They are not right and wrong — they are two ends of a tradeoff you pick based on whether the workload is mostly writing or mostly reading.
What normalization actually does
The classic normal forms (1NF, 2NF, 3NF) are a formal way to remove redundancy, but the intuition is simple: don't store the same fact twice, and don't store a fact in a table it doesn't belong to. If a customer's address lives in one customers row and every order references that customer by customer_id, then changing the address is one update in one place. Nothing can disagree with itself, because there is only one copy.
That is the real prize of normalization: integrity. There is no way for two rows to hold contradictory versions of the same fact, because the fact exists once. It also keeps writes cheap — an update touches one row, not hundreds. This is exactly what transactional systems want, which is why OLTP1 databases are normalized.
🔗 Learn more — 1 OLTP vs OLAP: two opposite jobs
The cost is joins. Answering "show me each order with the customer's name and the product's price" means reassembling data the schema deliberately split apart. On a transactional workload — small, point-lookup queries — that is fine. On an analytical workload that scans millions of rows and joins a dozen tables, it hurts.
What denormalization buys
Denormalization trades integrity and write-cost for read speed. You pre-join, pre-compute, and duplicate so that the read is a single scan with no joins. A reporting table might carry the customer name and product price right next to each order line, copied in at load time. The query gets fast and simple; the price is that the same fact now lives in many rows, and keeping those copies consistent is now your job, not the database's.
That is why denormalization belongs to the read side — OLAP, warehouses, analytics, and the star schema2, where a fact table sits surrounded by denormalized dimension tables precisely to make aggregation fast. Columnar storage softens the join penalty, but the modeling instinct survives: shape the data for how it will be read.
🔗 Learn more — 2 Star schema and dimensional modeling
The rule that actually holds
Don't pick one globally. The durable pattern is to normalize the source of truth and denormalize for serving. Keep a clean, normalized system of record where writes are correct and integrity is enforced by construction. Then build denormalized, read-optimized copies downstream — materialized tables, marts, wide reporting models — derived from that source and rebuilt when it changes.
The mistake is choosing by ideology instead of by access pattern. A write-heavy, correctness-critical system that someone denormalized "for performance" becomes a consistency nightmare. A scan-heavy analytics table left fully normalized becomes a join you pay for on every query. The schema should match the shape of the work: normalize where you write, denormalize where you read.