PrepZone Logo
PrepZone

Normalization and Denormalization

1NF through 3NF for clean schemas, and when to break rules for read performance.

Why this matters

  • Over-normalised schemas force five-table JOINs on every product page; under-normalised schemas let one address change corrupt hundreds of shipped labels.
  • Interviewers ask for 1NF–3NF definitions and when to break them — both sides matter in production.
  • VaultCommerce's reporting team and checkout team often disagree on schema shape; normalization vocabulary settles the debate.
  • Understanding anomalies (update, insert, delete) explains why duplicate customer_email on every order row is dangerous.
Clustered (PK)
Table rows (sorted by PK)
Row 1id=1
Row 2id=2
Row 3id=3
Non-clustered (email)
email index→ row pointer
Table heapunordered rows
A clustered index sorts table rows by key. Non-clustered indexes point to row locations.

First normal form (1NF)

Each column holds atomic values; no repeating groups. Bad design stores multiple tags in one column:

Java
-- Violates 1NF
CREATE TABLE products_bad (
    id   BIGSERIAL PRIMARY KEY,
    name TEXT,
    tags TEXT  -- 'sale,wireless,bestseller'
);

VaultCommerce uses a product_tags junction table — one row per tag assignment — satisfying 1NF.

Second normal form (2NF)

2NF requires 1NF plus every non-key column depends on the entire primary key. On a composite key (order_id, product_id), product_name must not live in order_items if it depends only on product_id:

Java
-- 2NF: product name lives in products, not order_items
CREATE TABLE order_items (
    order_id     BIGINT,
    product_id   BIGINT,
    quantity     INTEGER,
    unit_price_cents INTEGER,  -- depends on (order_id, product_id) snapshot
    PRIMARY KEY (order_id, product_id)
);

unit_price_cents depends on the line — it is a valid snapshot. product_name belongs in products.

Third normal form (3NF)

3NF requires 2NF plus no transitive dependencies — non-key columns must depend only on the primary key, not on other non-key columns.

Java
-- Violates 3NF: category_name depends on category_id, not product id
CREATE TABLE products_bad (
    id            BIGSERIAL PRIMARY KEY,
    name          TEXT,
    category_id   BIGINT,
    category_name TEXT  -- transitive dependency
);

-- 3NF: category name in categories table
CREATE TABLE categories (
    id   BIGSERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

Without normalization, updating a category name touches thousands of product rows (update anomaly), and deleting the last product in a category can orphan the category label (delete anomaly).

When to denormalize

Read-heavy paths that cannot tolerate multi-table JOINs justify controlled duplication:

Java
-- Denormalized summary table refreshed nightly
CREATE TABLE product_sales_summary (
    product_id    BIGINT PRIMARY KEY,
    units_sold_30d INTEGER NOT NULL,
    revenue_cents_30d BIGINT NOT NULL,
    refreshed_at  TIMESTAMPTZ NOT NULL
);

The ETL job aggregates from normalized order_items; the product listing API reads one row per SKU. Staleness of up to 24 hours is acceptable for "bestseller" badges.

VaultCommerce denormalization triggers

  • Read latency — Hot catalog endpoints cannot JOIN five tables at 10k RPS.
  • Reporting snapshots — Finance needs frozen monthly totals, not live recalculation.
  • Search indexes — Elasticsearch documents embed category names for facet filters.

Balancing the trade-off

AspectNormalized (3NF)Denormalized
WritesSingle update pointMultiple copies to sync
ReadsJOINs requiredSingle-table fetch
IntegrityConstraints enforce truthApplication or ETL must reconcile
VaultCommerce useOrders, paymentsBestseller badges, search docs
  • Writes

    Normalized (3NF)Single update point
    DenormalizedMultiple copies to sync
  • Reads

    Normalized (3NF)JOINs required
    DenormalizedSingle-table fetch
  • Integrity

    Normalized (3NF)Constraints enforce truth
    DenormalizedApplication or ETL must reconcile
  • VaultCommerce use

    Normalized (3NF)Orders, payments
    DenormalizedBestseller badges, search docs

Start normalized; denormalize only when profiling proves JOIN cost exceeds maintenance burden.

Quick recall

Everything you need if you only revisit this box.

  • 1NF eliminates repeating groups; 2NF removes partial key dependencies; 3NF removes transitive dependencies.
  • Normalization prevents update, insert, and delete anomalies.
  • VaultCommerce keeps orders normalized; summary and search tables denormalize for read speed.
  • Denormalize only with a refresh strategy and documented staleness tolerance.
  • Default to 3NF for transactional data; break rules for measured read bottlenecks.

Test yourself

Answer these before moving on — recall is what makes it stick.