GoCode logoGoCode

DBMS Normalization Explained: 1NF, 2NF and 3NF Without the Jargon

GoCode Team22 July 20264 min read

Normalization is usually taught as a list of rules to memorise for an exam, which is why most people can recite "no partial dependency" and still design a table that corrupts itself in a month. It's much simpler than the vocabulary suggests: store every fact exactly once. The normal forms are just the steps that get you there.

The table we're going to fix

A course-enrolment table that someone built in a hurry:

enrolments(
  student_id, student_name, student_phones,
  course_id, course_name, instructor_name, instructor_email
)

Three things will go wrong with it, and they have names:

  • Update anomaly — an instructor changes their email; you must update it in every row of every course they teach, and if you miss one the database now disagrees with itself.
  • Insertion anomaly — you can't add a new course until at least one student enrols, because there's nowhere to put a course without a student.
  • Deletion anomaly — the last student drops the course and the course, its name and its instructor vanish with them.

1NF — one value per cell

student_phones holds "98765xxxxx, 91234xxxxx". That breaks First Normal Form: every column must hold a single, atomic value, and there must be no repeating groups (phone1, phone2, phone3 is the same sin in a different outfit).

Fix: move phones into their own table, one row per phone.

student_phones(student_id, phone)

Why it matters practically: you cannot index, search or validate a comma-separated list. "Find the student with this number" becomes a LIKE '%...%' scan — slow and wrong at the edges.

2NF — no partial dependency on a composite key

The key of enrolments is (student_id, course_id) — a student enrols in a course. Now look at student_name: it depends only on student_id, which is half the key. That's a partial dependency, and it's why the student's name is repeated in every row they enrol in.

Second Normal Form: every non-key column must depend on the whole key. Split it:

students(student_id, student_name)
courses(course_id, course_name, instructor_name, instructor_email)
enrolments(student_id, course_id, enrolled_on, grade)

The insertion anomaly is already gone — a course can now exist with zero students.

3NF — no non-key column depending on another non-key column

courses still has a problem. instructor_email doesn't really depend on the course — it depends on instructor_name, which is itself a non-key column. That's a transitive dependency, and it's the update anomaly we started with: change one instructor's email and you're editing many rows.

Third Normal Form: every non-key column depends on the key, the whole key, and nothing but the key.

instructors(instructor_id, instructor_name, instructor_email)
courses(course_id, course_name, instructor_id)

Now each fact lives in exactly one place. An instructor's email is stored once; changing it is a one-row UPDATE. That's the whole point of normalization, and it's the sentence to say in an interview.

BCNF, briefly

Boyce-Codd Normal Form is a stricter 3NF: for every dependency, the left-hand side must be a candidate key. It only differs from 3NF in tables with overlapping candidate keys — rare in practice, occasionally asked about. Knowing that it's "3NF with the edge case closed" is usually enough.

When to deliberately denormalise

Normalization optimises for correctness on writes. Reads pay the bill: fully normalised data means more joins, and on a very large read-heavy table those joins cost real time. So production systems denormalise on purpose — a cached order_total, a duplicated course_name on a reporting table, a materialised summary.

Normalise until it hurts, denormalise until it works — but only with a measurement in hand, and only once you've accepted the job of keeping the duplicated copy in sync.

The important part is that denormalisation is a decision, made after profiling, with a plan for consistency. An accidental repeated column is not denormalisation; it's the bug this whole article is about.

Answering this in an interview

Don't recite definitions. Take one messy table, name the three anomalies, then fix it in three moves — atomic values, split on the partial dependency, split on the transitive one. It takes ninety seconds and demonstrates far more than the definitions do. Pair it with the query questions freshers get asked, and practise writing the resulting joins against a live database — because normalised data is exactly what makes joins unavoidable.

Want to try GoCode?

Get started