Concept · Storage & databases

Normalization vs denormalization

Store each fact once, or copy it where it’s read — and how to keep the copies honest.

3 mindenormalizematerialized view

Normalization stores each fact in one place: a customer's name lives in the customers table, and orders reference it by ID. Denormalization copies data to where it's read: each order row also stores the customer's name.

Normalized

  • Updates touch one row; there's no way for copies to disagree.
  • Reads that need related data use joins, which a relational database does well with the right indexes.
  • The default for transactional data. The SQL roadmap's schema-design lesson covers normal forms.

Denormalized

  • Reads are one lookup with no joins — essential in stores that don't join (key-value, wide-column) and useful at very high read rates.
  • Every copy must be updated when the fact changes, and copies can disagree in between.

Kinds of denormalization

  • Snapshots that should not change. An order's shipping address and item prices should be copied at purchase time: a later change to the customer's address must not rewrite history. This isn't a compromise; it's correct modelling.
  • Read-optimised copies. A feed entry that embeds the author's name and avatar; a product listing with its review count. Fast to read, needs updating.
  • Materialized views and precomputed aggregates. "Sales per day per store" maintained incrementally or refreshed periodically.

Keeping copies honest

  • Derive copies from a single source of truth and propagate changes asynchronously through events (transactional outbox), accepting that they lag briefly.
  • Prefer copying IDs and looking up mutable fields at read time (with a cache) when staleness would be visible or harmful — the news-feed lesson's timelines store post IDs for exactly this reason.
  • Be able to rebuild a denormalized copy from the source when it drifts.

Rule of thumb: normalize the system of record; denormalize in derived, rebuildable read models when a measured read path needs it. See also event sourcing and CQRS.

Learn it in depth