Normalization is the formal process of organizing relational data to reduce redundancy and prevent anomalies. Without it, a database grows into a tangle where updating a customer's city requires touching hundreds of rows, and where deleting an order accidentally erases the only record of a product. The normal forms — 1NF, 2NF, 3NF — are a systematic checklist for avoiding those traps.
What changed in 2026
- The normal forms themselves have not changed — they are mathematical properties, not fashions. What has changed is the tooling for analysing and enforcing them.
- Modern ORMs like Drizzle and Prisma encourage normalization through their relation APIs but do not enforce it — the discipline is still on the designer.
- AI schema assistants (Copilot, Cursor) can suggest normalized schemas, but they also confidently produce denormalized designs. Understand the theory to evaluate the output.
- JSONB columns in Postgres are widely misused as a way to skip normalization — knowing the theory helps you decide when JSONB is genuinely appropriate vs when it is just lazy design.
The anomalies normalization prevents
Consider a single flat table orders:
order_id | customer_name | customer_city | product_name | product_price | qty
---------|---------------|---------------|--------------|---------------|----
1 | Alice | London | Keyboard | 79.99 | 1
2 | Alice | London | Mouse | 29.99 | 2
3 | Bob | Paris | Keyboard | 79.99 | 1
Three problems:
- Update anomaly: if Alice moves to Berlin, you must update every one of her rows.
- Insert anomaly: you cannot add a product to the catalog without creating a fake order.
- Delete anomaly: deleting order 3 removes the only record of Bob's existence.
Normalization eliminates all three by putting each fact in exactly one place.
First Normal Form (1NF)
Rules:
- Every column contains atomic (indivisible) values.
- No repeating groups or arrays.
- Each row is uniquely identifiable (has a primary key).
Violation example:
-- BAD: tags is a comma-separated list (non-atomic)
CREATE TABLE articles (
id INT PRIMARY KEY,
title TEXT,
tags TEXT -- "tech, python, databases"
);
1NF fix:
CREATE TABLE articles (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL
);
CREATE TABLE article_tags (
article_id BIGINT NOT NULL REFERENCES articles(id),
tag TEXT NOT NULL,
PRIMARY KEY (article_id, tag)
);
Second Normal Form (2NF)
Rule: The table must be in 1NF, and every non-key column must depend on the entire primary key — no partial dependencies.
This only applies to tables with composite primary keys.
Violation example:
-- order_items with composite PK (order_id, product_id)
-- product_name depends only on product_id, not the whole key
order_id | product_id | product_name | qty | price
---------|------------|--------------|-----|------
1 | 42 | Keyboard | 1 | 79.99
product_name depends on product_id alone — a partial dependency.
2NF fix:
CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(10,2) NOT NULL
);
CREATE TABLE order_items (
order_id BIGINT NOT NULL REFERENCES orders(id),
product_id BIGINT NOT NULL REFERENCES products(id),
qty INT NOT NULL CHECK (qty > 0),
unit_price NUMERIC(10,2) NOT NULL, -- snapshot at time of purchase
PRIMARY KEY (order_id, product_id)
);
Third Normal Form (3NF)
Rule: The table must be in 2NF, and no non-key column should depend on another non-key column (no transitive dependencies).
Violation example:
-- employees table
id | name | department_id | department_name
---|-------|---------------|----------------
1 | Alice | 10 | Engineering
2 | Bob | 10 | Engineering
department_name depends on department_id (a non-key column), not directly on id.
3NF fix:
CREATE TABLE departments (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE employees (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
department_id INT NOT NULL REFERENCES departments(id)
);
Summary of the normal forms
| Normal Form |
Eliminates |
Key question |
| 1NF |
Non-atomic values, no PK |
Are all values atomic? Is each row unique? |
| 2NF |
Partial dependencies |
Does every non-key column need the whole PK? |
| 3NF |
Transitive dependencies |
Does every non-key column depend directly on the PK? |
| BCNF |
Edge cases 3NF misses |
Is every determinant a candidate key? |
| 4NF |
Multi-valued dependencies |
Rarely needed in practice |
When to deliberately denormalize
Normalization is for write correctness. Denormalization trades some correctness for read speed:
| Situation |
Denormalization pattern |
| Reporting queries joining 8 tables |
Materialized view or data warehouse table |
| "User's full name" shown everywhere |
Cached denormalized column with trigger |
| Order total queried constantly |
Stored total_cents column computed at insert |
| Analytics dashboard |
OLAP store (DuckDB, BigQuery, Redshift) separate from OLTP |
The rule: normalize your primary OLTP schema, denormalize specifically for measured read bottlenecks.
How to pick: normalize or leave it
- Is this a write-heavy transactional table? → Normalize to 3NF.
- Are there update or delete anomalies today? → Normalize.
- Is this a read-only reporting view or analytics table? → Denormalize or use materialized views.
- Is the duplication intentional as a historical snapshot (e.g.,
unit_price on order_items)? → Leave it; snapshots are correct denormalization.
Common mistakes
Over-normalizing to BCNF or 4NF in a standard OLTP system adds joins without meaningful benefit. 3NF is the correct stopping point for most applications.
Treating normalization as optional because "we can always fix it later." Schema migrations on large tables under live traffic are expensive and risky. Get it right early.
Storing snapshots as if they are live references. The unit_price on an order line item is correct denormalization — you want the price at purchase time, not the current price.
Normalizing lookup tables but not using foreign keys. The FK constraint is what makes the normalization enforceable; without it, the relationship is just a naming convention.
What to skip
- Normalizing JSONB columns for truly dynamic per-row schema — JSONB columns are appropriate for semi-structured data, and normalizing them into many sparse tables creates worse problems.
- Full normalization of event sourcing / audit log tables — append-only event tables are intentionally denormalized for replay and audit purposes.
- 5NF and Domain-Key Normal Form unless you are working on a formal academic or highly constrained enterprise schema — the return on effort is marginal.
FAQ
Do I need to know normal form names for everyday work?
Yes — the concepts are more important than the names. Understanding partial and transitive dependencies is exactly what separates clean schemas from ones that require full-table scans to update a company name.
What about NoSQL databases — do normal forms apply?
Partially. Document databases like MongoDB denormalize deliberately; the embedding vs referencing choice maps loosely to the same tradeoffs. The anomalies still exist; NoSQL just asks you to manage them explicitly.
How do I check if my existing schema violates 3NF?
Look for any column whose value is determined by another non-key column. If you can predict column C from column B without looking at the primary key, you have a transitive dependency.
Does normalization hurt performance?
Joining normalized tables adds CPU work at query time. For most OLTP queries with proper indexes this is negligible. When it becomes measurable, use materialized views or selective denormalization — not "normalize nothing."
Where to go next