π What is 3NF (Third Normal Form)?
A table is in 3NF if:
It is already in 2NF, and
No non-prime attribute is transitively dependent on the primary key
π What is Transitive Dependency?
A transitive dependency exists when:
A β B and B β C β A β Cπ In database terms: A non-key attribute depends on another non-key attribute, instead of depending directly on the primary key.
π§Ύ Example (Table in 2NF, but not in 3NF):
π Student_Detail Table:
Student_ID | Name | Dept_ID | Dept_Name |
|---|---|---|---|
101 | Swati | D1 | CSE |
102 | Ravi | D2 | IT |
β
Primary Key = Student_ID
β Dept_Name depends on Dept_ID
β
Dept_IDdepends onStudent_IDβ‘οΈ So,
Dept_Namedepends transitively onStudent_IDβ‘οΈ Violates 3NF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Third Normal Form (3NF)</title>
<style>
.infographic-threenf {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #16a34a;
--good-soft: #dcfce7;
--bad: #dc2626;
--bad-soft: #fee2e2;
--display-font: Georgia, 'Iowan Old Style', serif;
--body-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--mono-font: 'SF Mono', Consolas, monospace;
background: var(--paper);
color: var(--ink);
font-family: var(--body-font);
max-width: 900px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-threenf[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4ade80;
--good-soft: #123420;
--bad: #f87171;
--bad-soft: #3a1414;
}
.infographic-threenf * { box-sizing: border-box; }
.nf3-head { text-align: center; margin-bottom: 0.25rem; }
.nf3-head h2 {
font-family: var(--display-font);
font-size: 1.95rem;
margin: 0 0 0.5rem;
}
.nf3-head p {
color: var(--ink-soft);
font-size: 1rem;
margin: 0 auto;
max-width: 600px;
line-height: 1.55;
}
.nf3-frame {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.25rem;
margin-top: 1.75rem;
}
.nf3-frame-title { font-family: var(--display-font); font-size: 1.05rem; margin: 0 0 0.4rem; }
.nf3-frame-desc { font-size: 0.87rem; color: var(--ink-soft); line-height: 1.5; margin: 0 0 1rem; }
.nf3-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
table.nf3-table {
width: 100%;
min-width: 420px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.88rem;
}
table.nf3-table th, table.nf3-table td {
border: 1px solid var(--line);
padding: 0.5rem 0.7rem;
text-align: left;
white-space: nowrap;
}
table.nf3-table th { background: var(--accent); color: #fff; font-weight: 700; font-size: 0.75rem; }
table.nf3-table th.is-key { background: var(--ink-soft); }
.nf3-chain-builder { margin-top: 1.25rem; }
.nf3-chain-label { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); margin: 0 0 0.6rem; }
.nf3-pills { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 1rem; }
.nf3-pill {
font: inherit;
font-size: 0.85rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 999px;
padding: 0.5rem 1rem;
cursor: pointer;
color: var(--ink);
}
.nf3-pill:hover { border-color: var(--accent); }
.nf3-pill.is-used { background: var(--good-soft); color: var(--good); border-color: var(--good); cursor: default; }
.nf3-chain-display {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
min-height: 2.4rem;
padding: 0.6rem 0.8rem;
background: var(--paper);
border: 1px dashed var(--line);
border-radius: 8px;
font-family: var(--mono-font);
font-size: 0.9rem;
margin-bottom: 0.9rem;
}
.nf3-chain-node {
background: var(--accent-soft);
color: var(--accent);
border-radius: 6px;
padding: 0.25rem 0.6rem;
opacity: 0;
transform: scale(0.9);
animation: nf3-pop-in 0.3s ease forwards;
}
@keyframes nf3-pop-in { to { opacity: 1; transform: scale(1); } }
.nf3-chain-arrow { color: var(--ink-soft); }
.nf3-chain-placeholder { color: var(--ink-soft); font-style: italic; font-size: 0.85rem; }
.nf3-feedback {
padding: 0.65rem 0.85rem;
border-radius: 8px;
font-size: 0.85rem;
line-height: 1.5;
display: none;
margin-bottom: 0.9rem;
}
.nf3-feedback.is-visible { display: block; }
.nf3-feedback.fb-good { background: var(--good-soft); color: var(--good); }
.nf3-feedback.fb-bad { background: var(--bad-soft); color: var(--bad); }
.nf3-decompose-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.nf3-decompose-btn {
font: inherit;
font-size: 0.88rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 7px;
padding: 0.6rem 1.2rem;
cursor: pointer;
}
.nf3-decompose-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.nf3-decompose-hint { font-size: 0.78rem; color: var(--ink-soft); }
.nf3-result { display: none; margin-top: 1.5rem; }
.nf3-result.is-visible { display: block; animation: nf3-fade 0.3s ease; }
@keyframes nf3-fade { from { opacity: 0; } to { opacity: 1; } }
.nf3-arrow-down { text-align: center; font-size: 1.3rem; color: var(--ink-soft); margin-bottom: 1rem; }
.nf3-tables-row { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.nf3-table-block { flex: 1 1 220px; min-width: 0; }
.nf3-table-name { font-family: var(--display-font); font-size: 0.92rem; margin: 0 0 0.5rem; }
table.nf3-result-table {
width: 100%;
min-width: 200px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.85rem;
}
table.nf3-result-table th, table.nf3-result-table td {
border: 1px solid var(--line);
padding: 0.45rem 0.6rem;
text-align: left;
white-space: nowrap;
}
table.nf3-result-table th { background: var(--good); color: #fff; font-weight: 700; font-size: 0.7rem; }
.nf3-reset-btn {
font: inherit;
font-size: 0.82rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 7px;
padding: 0.45rem 0.9rem;
cursor: pointer;
color: var(--ink);
margin-top: 1.25rem;
}
.nf3-reset-btn:hover { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
.infographic-threenf *, .infographic-threenf *::before, .infographic-threenf *::after {
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-threenf" id="threenf">
<div class="nf3-head">
<p>A table is in 3NF when it's already in 2NF, and no non-key attribute depends on another non-key attribute β only on the primary key, directly.</p>
</div>
<div class="nf3-frame">
<p class="nf3-frame-title">Trace the transitive chain</p>
<p class="nf3-frame-desc">Primary Key = <code>Student_ID</code>. Click the columns in order to build the chain: what does Student_ID determine first, and what does <i>that</i> column determine next?</p>
<div class="nf3-table-scroll">
<table class="nf3-table">
<thead>
<tr><th class="is-key">Student_ID</th><th>Name</th><th>Dept_ID</th><th>Dept_Name</th></tr>
</thead>
<tbody>
<tr><td>101</td><td>Swati</td><td>D1</td><td>CSE</td></tr>
<tr><td>102</td><td>Ravi</td><td>D2</td><td>IT</td></tr>
</tbody>
</table>
</div>
<div class="nf3-chain-builder">
<p class="nf3-chain-label">Build the chain</p>
<div class="nf3-chain-display" id="nf3ChainDisplay">
<span class="nf3-chain-placeholder">Click a column below to start...</span>
</div>
<div class="nf3-pills">
<button class="nf3-pill" data-col="name" type="button">Name</button>
<button class="nf3-pill" data-col="deptId" type="button">Dept_ID</button>
<button class="nf3-pill" data-col="deptName" type="button">Dept_Name</button>
</div>
<div class="nf3-feedback" id="nf3Feedback"></div>
<div class="nf3-decompose-row">
<button class="nf3-decompose-btn" id="nf3DecomposeBtn" disabled type="button">Decompose into 3NF</button>
<span class="nf3-decompose-hint" id="nf3Hint">Build the full chain to unlock this.</span>
</div>
</div>
<div class="nf3-result" id="nf3Result">
<div class="nf3-arrow-down">β</div>
<div class="nf3-tables-row">
<div class="nf3-table-block">
<p class="nf3-table-name">Student</p>
<table class="nf3-result-table">
<thead><tr><th>Student_ID</th><th>Name</th><th>Dept_ID</th></tr></thead>
<tbody>
<tr><td>101</td><td>Swati</td><td>D1</td></tr>
<tr><td>102</td><td>Ravi</td><td>D2</td></tr>
</tbody>
</table>
</div>
<div class="nf3-table-block">
<p class="nf3-table-name">Department</p>
<table class="nf3-result-table">
<thead><tr><th>Dept_ID</th><th>Dept_Name</th></tr></thead>
<tbody>
<tr><td>D1</td><td>CSE</td></tr>
<tr><td>D2</td><td>IT</td></tr>
</tbody>
</table>
</div>
</div>
<div class="nf3-feedback fb-good is-visible" style="margin-top:1rem;">β
Dept_Name now lives with Dept_ID, and Dept_ID stays as a foreign key in Student. Every non-key attribute depends directly on its table's primary key β no transitive path required. This is 3NF.</div>
</div>
<button class="nf3-reset-btn" id="nf3Reset" type="button">Reset exercise</button>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('threenf');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const pills = document.querySelectorAll('.nf3-pill');
const chainDisplay = document.getElementById('nf3ChainDisplay');
const feedback = document.getElementById('nf3Feedback');
const decomposeBtn = document.getElementById('nf3DecomposeBtn');
const hint = document.getElementById('nf3Hint');
const result = document.getElementById('nf3Result');
const labels = { name: 'Name', deptId: 'Dept_ID', deptName: 'Dept_Name' };
let chain = [];
function showFeedback(text, good) {
feedback.textContent = text;
feedback.className = 'nf3-feedback is-visible ' + (good ? 'fb-good' : 'fb-bad');
}
function renderChain() {
if (chain.length === 0) {
chainDisplay.innerHTML = '<span class="nf3-chain-placeholder">Click a column below to start...</span>';
return;
}
const nodes = ['Student_ID', ...chain.map(c => labels[c])];
chainDisplay.innerHTML = nodes.map((n, i) =>
(i > 0 ? '<span class="nf3-chain-arrow">β</span>' : '') + '<span class="nf3-chain-node">' + n + '</span>'
).join('');
}
pills.forEach(pill => {
pill.addEventListener('click', () => {
const col = pill.dataset.col;
if (pill.classList.contains('is-used')) return;
if (col === 'name') {
showFeedback('β Name depends directly on Student_ID β it\u2019s not part of a transitive chain, so it doesn\u2019t belong here.', false);
return;
}
if (col === 'deptId' && chain.length === 0) {
chain.push('deptId');
pill.classList.add('is-used');
renderChain();
showFeedback('βοΈ Right β Student_ID determines Dept_ID first. Now, what does Dept_ID determine?', true);
hint.textContent = '1 of 2 links found β keep going.';
return;
}
if (col === 'deptName' && chain.length === 1 && chain[0] === 'deptId') {
chain.push('deptName');
pill.classList.add('is-used');
renderChain();
showFeedback('βοΈ Exactly β Dept_ID determines Dept_Name. So Student_ID β Dept_ID β Dept_Name: a transitive dependency. That violates 3NF.', true);
decomposeBtn.disabled = false;
hint.textContent = 'Chain complete! Ready to decompose.';
return;
}
if (col === 'deptName' && chain.length === 0) {
showFeedback('β Dept_Name isn\u2019t reached directly from Student_ID β what does Student_ID determine first?', false);
return;
}
});
});
decomposeBtn.addEventListener('click', () => {
result.classList.add('is-visible');
result.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
});
document.getElementById('nf3Reset').addEventListener('click', () => {
chain = [];
pills.forEach(p => p.classList.remove('is-used'));
renderChain();
feedback.className = 'nf3-feedback';
decomposeBtn.disabled = true;
hint.textContent = 'Build the full chain to unlock this.';
result.classList.remove('is-visible');
});
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});π― Why 3NF is Important?
Removes transitive dependencies
Prevents update anomalies
Makes schema cleaner and easier to maintain
π§ Key Concepts Recap:
Concept | In 3NF? |
|---|---|
Atomic values | β Yes (from 1NF) |
No partial dependencies | β Yes (from 2NF) |
No transitive dependencies | β Yes |
All attributes depend only on PK | β Yes |
β οΈ When is 3NF Not Enough?
If multiple candidate keys exist or non-trivial FDs donβt originate from candidate keys β you may need to apply BCNF (Boyce-Codd Normal Form).