Definition
- 2NF (Second Normal Form) – requires 1NF and that every non-key column depends on the *whole* composite primary key, not just part of it; eliminates partial dependencies.
- 3NF (Third Normal Form) – requires 2NF and that every non-key column depends *only* on the key, not on another non-key column; eliminates transitive dependencies (key → A → B is disallowed).
- Scope difference – 2NF is only meaningful for composite keys; 3NF applies to any key including single-column keys.
- Strict ladder – 3NF presupposes 2NF presupposes 1NF.
- BCNF – a stricter version of 3NF requiring every determinant to be a candidate key.
Imagine a National Bank of India spreadsheet keyed by (account number, product code). 2NF says: do not store a fact that depends on only the account number (like the branch name) in a table keyed by both – that fact is repeated for every product and belongs in its own account table. 3NF says: even in a table keyed correctly, do not store a fact that depends on another ordinary fact – if you keep the branch's city, and city is determined by the branch, then city belongs with the branch, not scattered through every account row.
Understanding the Concepts
What is a partial dependency (2NF violation)?
A partial dependency happens when a non-key column depends on only part of a composite primary key rather than the entire key. 2NF only becomes relevant when a table has a composite primary key.
-- VIOLATION: branch_name depends only on account_id, not on product_code
CREATE TABLE account_products (
account_id VARCHAR(10),
product_code VARCHAR(10),
branch_name VARCHAR(50), -- depends on account_id alone!
enrolled_at TIMESTAMP,
PRIMARY KEY (account_id, product_code)
);
account_id | product_code | branch_name | enrolled_at -----------+--------------+------------------+------------ SAV2001 | SAVINGS | Mumbai Central | 2024-01-01 SAV2001 | FIXED_DEP | Mumbai Central | 2024-02-01 ← duplicated! SAV2001 | LOAN | Mumbai Central | 2024-03-01 ← duplicated!
branch_name is duplicated for every product because it depends only on account_id. Changing the branch means updating many rows.
How do you fix a 2NF violation?
Split the partially dependent column into a table keyed by the column it actually depends on.
-- Fix: separate table for account-level facts
CREATE TABLE accounts (
account_id VARCHAR(10) PRIMARY KEY,
branch_name VARCHAR(50) NOT NULL
);
CREATE TABLE account_products (
account_id VARCHAR(10) REFERENCES accounts(account_id),
product_code VARCHAR(10),
enrolled_at TIMESTAMP,
PRIMARY KEY (account_id, product_code)
);
-- branch_name lives in one place. No duplication.
What is a transitive dependency (3NF violation)?
A transitive dependency is the chain key → A → B, where a non-key column depends on another non-key column instead of directly on the key.
-- VIOLATION: branch_city depends on branch_code, not on account_id
CREATE TABLE accounts (
account_id VARCHAR(10) PRIMARY KEY,
branch_code VARCHAR(10) NOT NULL,
branch_city VARCHAR(50) NOT NULL -- depends on branch_code, not account_id!
);
account_id | branch_code | branch_city -----------+-------------+------------ SAV2001 | BR001 | Mumbai ← duplicated! SAV2002 | BR001 | Mumbai ← duplicated! CUR3001 | BR002 | Delhi
branch_city is transitively dependent: account_id → branch_code → branch_city. It is repeated for every account at the same branch.
How do you fix a 3NF violation?
Move the transitively dependent column into a separate table keyed by the column it depends on.
-- Fix: separate table for branch-level facts
CREATE TABLE branches (
branch_code VARCHAR(10) PRIMARY KEY,
branch_city VARCHAR(50) NOT NULL
);
CREATE TABLE accounts (
account_id VARCHAR(10) PRIMARY KEY,
branch_code VARCHAR(10) NOT NULL REFERENCES branches(branch_code)
-- branch_city is no longer here; it lives in branches
);
How do 2NF and 3NF differ?
| Aspect | 2NF | 3NF |
|--------|-----|-----|
| Eliminates | Partial dependencies | Transitive dependencies |
| Relevant when | Composite primary key | Any key (single or composite) |
| Dependency shape | non-key depends on part of key | non-key depends on another non-key |
| Fix | Split partial-dependent column to table keyed by the part | Split transitive-dependent column to its own table |
| Prerequisite | 1NF | 2NF |
What anomalies do they prevent?
Both 2NF and 3NF prevent three anomalies caused by redundancy:
- Update anomaly – a fact stored in many rows must be updated in many places, risking inconsistency.
- Insert anomaly – you cannot record a fact because unrelated data is missing.
- Delete anomaly – deleting one row accidentally erases an unrelated fact.
Before 3NF (branch_city repeated in accounts): Update anomaly: Rename Mumbai to Mumbai City → update 1000 rows. Insert anomaly: Cannot record a new branch that has no accounts yet. Delete anomaly: Deleting the last account at BR002 erases Delhi's city. After 3NF (branch_city in branches table): Update: Change city in one place. Insert: Add a branch without any accounts. Delete: Removing accounts does not lose branch info.
What is BCNF?
Boyce-Codd Normal Form is a stricter version of 3NF. It requires that every determinant (anything that functionally determines another column) is a candidate key. It closes an edge case 3NF allows when there are overlapping candidate keys.
3NF allows: non-prime attribute determines another non-prime (if via candidate key)
BCNF forbids: any non-candidate-key determinant
In practice, most 3NF schemas are already in BCNF. The distinction matters mainly in academic settings or when candidate keys overlap.
How does this look in Java with JPA?
package com.bank.model;
import jakarta.persistence.*;
@Entity
@Table(name = "branches")
public class Branch {
@Id
@Column(name = "branch_code", length = 10)
private String branchCode;
@Column(name = "branch_city", nullable = false, length = 50)
private String branchCity;
}
@Entity
@Table(name = "accounts")
public class Account {
@Id
@Column(name = "account_id", length = 10)
private String accountId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "branch_code", nullable = false)
private Branch branch; // 3NF: city lives in Branch, not here
}
What are common interview follow-up questions?
- "Is a table with a single-column PK automatically in 2NF?" Yes, because partial dependencies require a composite key. With a single-column key there is nothing to partially depend on.
- "What is the difference between 3NF and BCNF?" BCNF is stricter: every determinant must be a candidate key, while 3NF allows non-prime determinants in certain overlapping key scenarios.
- "When would you not normalize to 3NF?" When read performance matters more than write correctness, you deliberately denormalize for faster reads.
Visual Overview
2NF Violation (partial dependency on composite key):
PK: (account_id, product_code)
├─────────────────────────→ enrolled_at ✓ depends on whole key
│
└── account_id alone ─────→ branch_name ✗ partial dependency!
Fix: Move branch_name to accounts table (keyed by account_id)
3NF Violation (transitive dependency):
PK: account_id
│
├─→ branch_code ✓ depends on key
│ │
│ └─→ branch_city ✗ transitive! (key → branch_code → branch_city)
│
Fix: Move branch_city to branches table (keyed by branch_code)
Normal Form Ladder:
1NF → 2NF → 3NF → BCNF
│ │ │ │
Atomic No No Every
values partial transit. determinant
deps deps is candidate key
Key Takeaway
2NF and 3NF both remove redundancy, but they target different dependencies. Second Normal Form applies only when a table has a composite primary key, and it requires that every non-key column depend on the whole key rather than on just part of it, eliminating partial dependencies. If a table is keyed by (account_id, product_code) but stores branch_name, which depends only on account_id, that is a partial dependency: branch_name is duplicated for every product, and the fix is to move branch facts into a table keyed by account_id alone.
Third Normal Form assumes the table is already in 2NF and requires that every non-key column depend only on the key and not on another non-key column, eliminating transitive dependencies. If an accounts table stores branch_code and branch_city where account_id determines branch_code and branch_code determines branch_city, then branch_city is transitively dependent. The fix is a separate branch table.
The crisp distinctions are: 2NF is only meaningful for composite keys while 3NF applies to any key, 2NF removes dependency on part of the key while 3NF removes dependency on another non-key column, and they form a strict ladder. At National Bank of India, a legacy reporting table keyed by account and product repeated branch names and cities on every row, so updating one branch's city meant rewriting hundreds of rows and a new branch with no accounts could not even be recorded. Splitting it put each fact in one place and the anomalies disappeared. BCNF tightens 3NF by demanding that every determinant be a candidate key.
Remember one sentence: 2NF kills partial dependencies on part of a composite key, while 3NF kills transitive dependencies on other non-key columns.
---