SQL Lesson 24: One Typo Broke 20 Rows (Here's Why)
π Full written solution: https://interview-kit-fe.vercel.app/sql-lesson-24-normalization-1nf-2nf-3nf π» Code on GitHub: https://github.com/timepasshub2539-hue/leetcode-python/tree/main/sql-lesson-24-normalization-1nf-2nf-3nf Chapters: 0:00 One Typo Broke 20 Rows 0:19 What Is a Table, Really? 0:36 The Villain: Redundancy 0:54 Anomalies: The Symptoms 1:11 The Messy Starting Table 1:25 First Normal Form (1NF) 1:41 Three Words: Keys 2:01 Second Normal Form (2NF) 2:23 Third Normal Form (3NF) 2:43 See the Split Happen 2:55 Common Mistakes 3:17 Don't Over-Normalize 3:40 The Three Forms at a Glance 3:57 Recap and Sign-Off One typo shouldn't corrupt 20 rows β but bad schema design makes it happen. In Lesson 24 we fix that with normalization. You'll learn what redundancy really costs, spot the insert/update/delete anomalies it causes, then walk a messy table through 1NF, 2NF, and 3NF step by step. We'll decode keys, watch the table split live, cover common mistakes, and see why over-normalizing can hurt too. By the end you'll design schemas that store every fact in exactly one place. #SQL #DatabaseDesign #Normalization #LearnSQL #DataEngineering Watch next: - 74% of Women Lived. 19% of Men Didn't. The Data Explains Why: https://youtu.be/xg_LtvWLmBw - You've Used These 10 Functions Thousands of Times #shorts: https://youtu.be/oZu7t8eNNpM - Common Mistake: Forgetting the Script Tag Placement #shorts: https://youtu.be/Ipc-RKtAhSg
8. Introduction
Change one phone number and suddenly your database disagrees with itself. Half your records say the new number, half still show the old one, and no one can tell which is correct. That mess isn't a bug in your code β it's a flaw in your schema.
Database normalization is the discipline that prevents it. If you're preparing for a coding interview, working as a backend or data engineering developer, or just trying to design tables you won't regret in six months, normalization is one of those foundational algorithms and data structuresβadjacent skills that separates people who use databases from people who design them.
Interviewers love this topic because it reveals how you think. Anyone can write a SELECT query. But can you explain why a table is badly designed? Can you spot the redundancy, name the anomaly it causes, and refactor the schema on a whiteboard? That's the real test β and it comes up constantly in software engineering system-design rounds.
By the end of this article you'll be able to walk any messy table through First, Second, and Third Normal Form, explain every decision out loud, and know when to stop normalizing on purpose.
9. Problem Overview
Picture a spreadsheet. Each row is one thing β say, one student. Each column describes that thing β name, age, department. A relational database is just a collection of these tables that can point at one another using keys.
The problem starts when a single table tries to describe many kinds of things at once. Imagine a table that stores students, their enrolled courses, and their department details all in one place. The department name "Computer Science" gets copied next to every single CS student. A student's list of courses gets crammed into one cell as "SQL, Python".
That repeated information is redundancy, and redundancy is the enemy. The same fact β one department name, one course title β physically exists in dozens of rows. When that fact changes, you must update every copy perfectly, or your data becomes inconsistent.
Normalization is the set of rules that eliminates this. Its entire job is simple: make sure every fact is written down in exactly one place. One table should describe one kind of thing, and nothing else.
10. Example
Here is our messy "before" table β one table trying to be three:
| student_id | student_name | courses | dept_id | dept_name |
|---|---|---|---|---|
| 1 | Anya | SQL, Python | D1 | Computer Science |
| 2 | Ravi | SQL | D1 | Computer Science |
| 3 | Meera | Math | D2 | Mathematics |
Look at everything wrong here:
- The
coursescell holds a list. Anya's row stuffs two courses into one cell. dept_namerepeats. "Computer Science" is written twice, once per CS student. Add 1,000 CS students and you've written it 1,000 times.- Facts are entangled. A student, a course, and a department are three different ideas sharing one row.
Now watch the three anomalies this causes:
- Update anomaly: The CS department is renamed. You update Anya's row but forget Ravi's. Now the database says CS is called two different things.
- Insert anomaly: You want to add a brand-new course, "Rust", but no student has enrolled yet. You can't β there's no student row to attach it to.
- Delete anomaly: Meera drops out and you delete her row. You've just erased the only record that the "Mathematics" department ever existed.
These three pains are exactly what normalization cures.
11. Intuition
Here's how an experienced engineer actually thinks about this β not by memorizing definitions, but by asking one question repeatedly:
"Is this fact allowed to live here?"
When you see the same value typed over and over, your instinct should fire: this fact wants its own home. The department name doesn't describe a student β it describes a department. So it belongs in a department table, written once, and referenced by everyone who needs it.
That's the whole mental model. Normalization isn't three scary textbook rules. It's a ladder of three questions about where each fact is allowed to live:
- Is every cell a single value? (atomic)
- Does every column depend on the whole key? (whole key)
- Does every column depend only on the key, not on another column? (nothing but the key)
Say it like a chant: atomic, whole key, nothing but the key. Once that rhythm is in your head, you'll spot violations instantly β long before you remember what "2NF" technically stands for.
12. Brute Force Solution
The idea: Just keep everything in one big table and handle consistency in your application code. Every time a department name changes, write code that finds and updates all matching rows.
The algorithm:
- Store all data in a single flat table.
- When a shared fact changes, run an
UPDATE ... WHEREacross every affected row. - Add validation logic in the app to keep duplicate values in sync.
Advantages:
- Dead simple to start. One table, no joins.
- Reads are fast β everything is in one place, no joining required.
Disadvantages:
- Every write is a landmine. Miss one row and your data is silently wrong.
- Insert and delete anomalies are unavoidable β you can't store a course without a student.
- The app carries logic the database should enforce for you.
Time complexity: A single "change one fact" operation becomes O(n) β you must touch every row containing the duplicated value. Space complexity: O(n Γ d) where d is the duplicated data per row β you pay for every copy.
This "solution" works until it doesn't, and it fails in the worst way: quietly.
13. Optimal Solution
The optimal approach is to normalize through 1NF β 2NF β 3NF, peeling one layer of mess off at a time.
Step 1 β First Normal Form (1NF): One value per cell
Rule: No cell may hold a list. Every value is atomic.
Anya's "SQL, Python" becomes two separate rows. It's like putting each item on its own line of a receipt instead of scribbling them all in one box.
| student_id | student_name | course | dept_id | dept_name |
|---|---|---|---|---|
| 1 | Anya | SQL | D1 | Computer Science |
| 1 | Anya | Python | D1 | Computer Science |
| 2 | Ravi | SQL | D1 | Computer Science |
| 3 | Meera | Math | D2 | Mathematics |
Keys β the ID cards of your rows
Before the next two rules, you need keys:
- Primary key: one column that uniquely names a row (like
student_id). - Composite key: when it takes two columns together to be unique β like
seat + flight_number. Here,(student_id, course)together identify one enrollment. - Non-key column: plain data riding along, like
student_name.
The next two forms are entirely about keys.
Step 2 β Second Normal Form (2NF): Depend on the whole key
Rule: When your key is composite, every non-key column must depend on the entire key β not just part of it.
Our key is (student_id, course). But student_name depends only on student_id β it has nothing to do with which course. That's a partial dependency. Fix it by pulling student_name into its own Student table.
Enrollment table:
| student_id | course |
|---|---|
| 1 | SQL |
| 1 | Python |
| 2 | SQL |
| 3 | Math |
Student table:
| student_id | student_name | dept_id |
|---|---|---|
| 1 | Anya | D1 |
| 2 | Ravi | D1 |
| 3 | Meera | D2 |
Step 3 β Third Normal Form (3NF): Nothing but the key
Rule: No non-key column may depend on another non-key column. That hidden chain is a transitive dependency.
In the Student table, dept_name depends on dept_id, which depends on student_id. The department name is riding on a middleman. Split departments into their own table.
Department table:
| dept_id | dept_name |
|---|---|
| D1 | Computer Science |
| D2 | Mathematics |
Now "Computer Science" is written once. Change it there, and every student sees the update instantly β because they were only ever pointing at it.
The whole journey
ONE crowded table β THREE focused tables
βββ Student (who they are)
βββ Enrollment (what they take)
βββ Department (where they belong)
Each table owns exactly one idea. The keys stitch them back together whenever you need the full picture.
14. SQL Code
Here's the clean, production-quality schema that results:
-- Department: each department's name lives here exactly once
CREATE TABLE department (
dept_id VARCHAR(10) PRIMARY KEY,
dept_name VARCHAR(100) NOT NULL
);
-- Student: describes a student, references their department
CREATE TABLE student (
student_id INT PRIMARY KEY,
student_name VARCHAR(100) NOT NULL,
dept_id VARCHAR(10) NOT NULL,
FOREIGN KEY (dept_id) REFERENCES department(dept_id)
);
-- Enrollment: one row = one student taking one course
CREATE TABLE enrollment (
student_id INT,
course VARCHAR(100),
PRIMARY KEY (student_id, course), -- composite key
FOREIGN KEY (student_id) REFERENCES student(student_id)
);
Reassembling the full picture is a simple join:
SELECT s.student_name,
e.course,
d.dept_name
FROM enrollment e
JOIN student s ON s.student_id = e.student_id
JOIN department d ON d.dept_id = s.dept_id;
15. Code Walkthrough
departmentis created first because other tables reference it β you can't point at a table that doesn't exist yet.dept_idis the primary key;dept_nameis stored a single time.studentholds facts that describe a student and only a student. TheFOREIGN KEY (dept_id)enforces that every student points to a real department β the database itself now guarantees consistency you used to hope for in app code.enrollmentusesPRIMARY KEY (student_id, course)β a composite key. This says a student can take many courses and a course can have many students, but the same pairing can't be entered twice.- The
JOINquery proves nothing was lost. Normalization split the data for storage, but you rebuild any view you want on demand.
16. Dry Run
Let's rename "Computer Science" to "CS & Engineering".
Before normalization, that fact lived in every CS student's row:
UPDATE big_table SET dept_name = 'CS & Engineering' WHERE dept_id = 'D1';
-- touches Anya's rows AND Ravi's rows β miss one and data is inconsistent
After normalization, it lives in exactly one row:
UPDATE department SET dept_name = 'CS & Engineering' WHERE dept_id = 'D1';
One row updated. Anya and Ravi both instantly see the new name, because they never stored it β they only stored dept_id = 'D1' and point at the department. Zero chance of disagreement. That's the entire payoff of normalization in a single query.
17. Complexity Analysis
Storage: Normalization reduces duplicated data from O(n Γ d) down to storing each shared fact once β O(unique facts). A department name shared by 10,000 students is stored one time, not 10,000 times.
Write cost: Changing a shared fact drops from O(n) (update every copy) to O(1) (update the single source row).
Read cost: This is the trade-off. Reassembling data now requires joins, and each join has a cost. A query that was a single-table scan may now join three tables. For most applications this is negligible thanks to indexed keys β but it's real, and it's why the next section matters.
18. Alternative Solutions β When to Denormalize
Here's an honest opinion from the field: 3NF is the sweet spot for almost every application. The higher normal forms (BCNF, 4NF, 5NF) exist, but for everyday apps they're mostly overkill.
And sometimes you should go the other direction. If you split everything into a hundred tiny tables, every query joins ten of them, and reads get slow. For read-heavy dashboards and analytics, engineers deliberately denormalize β keeping a little redundancy on purpose for speed.
| Approach | Best for | Cost |
|---|---|---|
| Fully normalized | Transactional apps, writes | Slower, join-heavy reads |
| Denormalized | Dashboards, analytics, reads | Redundancy you must manage |
The rule: normalize by default, then relax it only where the numbers force you to.
19. Edge Cases
- Empty tables: An enrollment with no students should still allow the course to exist somewhere β normalization already fixes this by separating courses/departments from students.
- Duplicates: The composite primary key on
enrollmentblocks the same studentβcourse pair from being inserted twice. - Orphaned references: Foreign keys prevent a student from pointing to a department that doesn't exist.
- Single-value lists: Even a cell with one comma-separated value is a 1NF violation waiting to grow.
- Null keys: A key column should never be null β a row with no identity can't be reliably referenced.
20. Common Mistakes
- The comma-separated cell. Storing
tags = "red, blue, green"or five phone numbers in one column. The moment one value changes, you're parsing strings by hand. This is the #1 beginner trap. - Bolting on more columns instead of a new table. When you feel yourself adding
phone1,phone2,phone3, stop β a hidden table is asking to be born. - Ignoring partial dependencies. Leaving a column that depends on only half a composite key inside the wrong table (a 2NF miss).
- Missing the transitive middleman. Letting a non-key column depend on another non-key column (a 3NF miss) β the sneakiest one to spot.
- Over-normalizing everything. Shredding data into so many tables that every read becomes a ten-way join. Normalization is a tool, not a religion.
- Forgetting foreign keys. Splitting tables but never enforcing the relationships, so orphaned data creeps back in.
21. Interview Questions
Be ready for these follow-ups:
- "What's the difference between 2NF and 3NF in one sentence?"
- "Give a real example of an update anomaly."
- "What is a composite key, and when do you need one?"
- "What is a transitive dependency?"
- "When would you deliberately denormalize a schema?"
- "Is a table in 3NF always in 2NF? Why?"
- "What does BCNF add beyond 3NF?"
22. Similar Problems
While normalization isn't a LeetCode-style algorithm problem, it connects directly to these interview and practice areas:
- LeetCode Database problems (e.g. Duplicate Emails, Employees Earning More Than Their Managers) β these test the joins and relationships that normalized schemas depend on.
- System Design: Design a URL shortener / social feed β normalization vs. denormalization trade-offs are central.
- Functional dependency and closure problems β the formal theory underneath 2NF and 3NF.
They're related because they all test the same core skill: reasoning about where a fact belongs and how tables relate.
23. Key Takeaways
- Redundancy is the root cause; anomalies (update, insert, delete) are the symptoms.
- 1NF: one value per cell β atomic.
- 2NF: every non-key column depends on the whole key.
- 3NF: no non-key column depends on another non-key column.
- Chant it: atomic, whole key, nothing but the key.
- Normalize by default; denormalize only when performance data demands it.
24. Watch the Video
Reading the rules is one thing β watching a messy table split into clean tables in real time makes it click. In the video, we walk this exact table through 1NF, 2NF, and 3NF live, decode the keys as we go, and show why over-normalizing can hurt.
βΆοΈ Watch Lesson 24 here: {LINK}
25. About the Series
This article is part of the Fun with Learning Technology series β a hands-on journey through SQL, databases, and the practical software engineering skills developers actually use on the job. Each lesson takes one real concept, strips away the textbook jargon, and teaches the intuition first, so the rules finally make sense. If you're building your SQL and backend foundations one lesson at a time, you're in the right place.
26. Call To Action
If this helped normalization finally click:
- π Like the video on YouTube β it genuinely helps the channel grow.
- π Subscribe so you never miss a lesson in the series.
- π© Subscribe to the Substack for the written deep-dives like this one.
- π¬ Comment with the schema mistake that once cost you an afternoon.
- π Share it with a developer who's still storing lists in one cell.
This bug took three hours to untangle. Subscribing takes three seconds.
The solution
StudentCourses
-------------------------------
id | name | courses | dept
1 | Anya | SQL, Python | CS
2 | Ravi | SQL | CS
3 | Meera | Python, Java | ITReady to try it yourself? Solve Sql problems with instant feedback in the practice sandbox.
Practice now β


