JavaScript 21 🧬 Mutability
Mutability refers to whether an object’s state can be changed after it’s created. Mutable objects can be changed; immutable ones cannot.
Understanding mutability is essential because JavaScript treats primitives and objects very differently.
A Quick Look at the Examples
// 1. Primitives are immutable — this creates a NEW value
let x = 5;
x = 10; // The original 5 is unchanged; a new number 10 is created
// 2. Objects are mutable — this modifies the ORIGINAL object
let obj = { a: 1 };
obj.a = 2; // The original object is modified
// 3. Object.freeze() makes objects immutable
const obj2 = { a: 1 };
Object.freeze(obj2);
obj2.a = 2; // Silently ignored (or throws in strict mode)
console.log(obj2.a); // 1 — unchanged
a. What is Mutability?
Mutability refers to whether an object’s state can be changed after it’s created. Mutable objects can be changed; immutable ones can’t.
Primitives Are Immutable
Primitive types — numbers, strings, booleans, null, undefined, symbols, BigInt — are immutable.
If you assign a variable to a number and then change it, that’s actually creating a new value.
let x = 5;
x = 10; // NOT modifying the original 5 — creating a new number 10
What happens:
- The variable
xoriginally points to the value5 - Assigning
x = 10makesxpoint to a new value10 - The original
5is unchanged (and may be garbage-collected)
Objects Are Mutable
Objects — arrays, objects, functions — are mutable.
If you have an object and change one of its properties, that modifies the original object.
let obj = { a: 1 };
obj.a = 2; // Changes the object — the original is modified
What happens:
objpoints to an object in memoryobj.a = 2modifies the object itself- Anyone else referencing the same object sees the change
The Default: Mutability
In JavaScript, the default for objects is mutability. But you can use Object.freeze() or other libraries to help with immutability.
b. Mutability Examples
JavaScript’s Primitive Types
JavaScript has six (or seven, with BigInt) primitive types:
| Type | Example | Mutable? |
|---|---|---|
number | 42, 3.14 | ❌ Immutable |
string | 'hello' | ❌ Immutable |
boolean | true, false | ❌ Immutable |
null | null | ❌ Immutable |
undefined | undefined | ❌ Immutable |
symbol | Symbol('id') | ❌ Immutable |
bigint | 123n | ❌ Immutable |
These are immutable by nature — their values cannot be changed after creation.
Example 1: Primitives Create New Values (Not Mutations)
let x = 5;
x = 10;
What happens:
- It creates a new number value (
10), not a mutation of the original (5) - The variable
xis reassigned — but the original value5is unchanged
Same for strings:
let str = "hello";
str = str.toUpperCase(); // "HELLO" — a NEW string
console.log(str); // "HELLO"
// The original "hello" is unchanged — it's a new string
Same for booleans:
let flag = true;
flag = false; // New boolean value — not a mutation
Example 2: Objects Are Mutable by Default
JavaScript objects (including arrays, functions, and objects) are mutable by default. Their properties can be changed after creation.
let obj = { a: 1 };
obj.a = 2; // Modifies the ORIGINAL object
console.log(obj); // { a: 2 }
Object references matter:
const obj1 = { a: 1 };
const obj2 = obj1; // Same reference!
obj2.a = 2;
console.log(obj1.a); // 2 — obj1 changed too!
Arrays are also mutable:
const arr = [1, 2, 3];
arr.push(4); // Modifies original
arr[0] = 99; // Modifies original
console.log(arr); // [99, 2, 3, 4]
Example 3: Object.freeze() Makes Objects Immutable
Object.freeze() freezes an object, making its properties non-writable, non-configurable, and non-enumerable.
const obj = { a: 1 };
Object.freeze(obj);
obj.a = 2; // ❌ No effect — change is silently ignored
console.log(obj.a); // 1
In strict mode, it throws an error:
'use strict';
const obj = { a: 1 };
Object.freeze(obj);
obj.a = 2; // ❌ TypeError: Cannot assign to read only property 'a'
What Object.freeze() prevents:
| Operation | Effect |
|---|---|
| Adding new properties | ❌ Fails |
| Removing properties | ❌ Fails |
| Changing existing properties | ❌ Fails |
| Changing property descriptors | ❌ Fails |
What Object.freeze() does NOT prevent:
- Mutating nested objects (shallow freeze)
const obj = {
name: 'Alice',
address: { city: 'NYC' }
};
Object.freeze(obj);
obj.name = 'Bob'; // ❌ Fails (frozen)
obj.address.city = 'LA'; // ✅ Works! (nested object not frozen)
console.log(obj.address.city); // 'LA'
For deep freeze, you need a recursive function:
function deepFreeze(obj) {
Object.freeze(obj);
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
deepFreeze(obj[key]);
}
});
return obj;
}
Example 4: const vs Object Mutation
const offers variable immutability — it prevents reassignment but not object mutation.
const obj = { a: 1 };
// ❌ Cannot reassign
// obj = { a: 2 }; // TypeError: Assignment to constant variable
// ✅ CAN mutate properties
obj.a = 2; // Works!
obj.b = 3; // Works!
delete obj.a; // Works!
console.log(obj); // { b: 3 }
Same for arrays:
const arr = [1, 2, 3];
// ❌ Cannot reassign
// arr = [4, 5, 6]; // TypeError
// ✅ CAN mutate
arr.push(4); // Works!
arr[0] = 99; // Works!
console.log(arr); // [99, 2, 3, 4]
Summary:
| Keyword | Reassignment | Mutation |
|---|---|---|
const | ❌ Not allowed | ✅ Allowed |
let | ✅ Allowed | ✅ Allowed |
var | ✅ Allowed | ✅ Allowed |
const + Object.freeze() | ❌ Not allowed | ❌ Not allowed |
Complete Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mutability</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f8f9fa;
color: #333;
line-height: 1.6;
}
h1 { color: #007bff; border-bottom: 3px solid #007bff; padding-bottom: 10px; }
h2 { color: #28a745; border-left: 4px solid #28a745; padding-left: 15px; margin-top: 30px; }
.demo-box {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin: 15px 0;
}
pre {
background: #1e1e1e;
color: #d4d4d4;
padding: 15px;
border-radius: 8px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.8;
}
.keyword { color: #569cd6; }
.string { color: #ce9178; }
.number { color: #b5cea8; }
.function { color: #dcdcaa; }
.comment { color: #6a9955; }
.boolean { color: #569cd6; }
.error { color: #f48771; }
.success { color: #4ec9b0; }
#output {
background: #e9ecef;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
min-height: 40px;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
border-left: 4px solid #007bff;
white-space: pre-wrap;
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
th, td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
}
th { background: #007bff; color: white; }
tr:nth-child(even) { background: #f8f9fa; }
.btn {
padding: 10px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 6px;
font-size: 1em;
font-weight: bold;
cursor: pointer;
margin: 5px;
transition: all 0.3s;
}
.btn:hover {
background: #0056b3;
transform: translateY(-2px);
}
.btn-success { background: #28a745; }
.btn-success:hover { background: #1e7e34; }
.btn-danger { background: #dc3545; }
.btn-danger:hover { background: #a71d2a; }
.state-display {
font-family: 'Courier New', monospace;
font-size: 1.1em;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
background: #f8f9fa;
border-left: 4px solid #007bff;
}
</style>
</head>
<body>
<h1>Mutability</h1>
<div class="demo-box">
<h2>1. Primitives Are Immutable</h2>
<pre>
<span class="keyword">let</span> x = <span class="number">5</span>;
x = <span class="number">10</span>; <span class="comment">// Creates a NEW value, doesn't modify the original 5</span>
<span class="keyword">let</span> str = <span class="string">"hello"</span>;
str = str.<span class="function">toUpperCase</span>(); <span class="comment">// Returns a NEW string "HELLO"</span>
</pre>
</div>
<div class="demo-box">
<h2>2. Objects Are Mutable</h2>
<pre>
<span class="keyword">let</span> obj = { a: <span class="number">1</span> };
obj.a = <span class="number">2</span>; <span class="comment">// Modifies the ORIGINAL object</span>
<span class="keyword">const</span> obj1 = { a: <span class="number">1</span> };
<span class="keyword">const</span> obj2 = obj1; <span class="comment">// Same reference!</span>
obj2.a = <span class="number">2</span>;
console.log(obj1.a); <span class="comment">// 2 — obj1 changed too!</span>
</pre>
</div>
<div class="demo-box">
<h2>3. Object.freeze() — Immutable Objects</h2>
<pre>
<span class="keyword">const</span> obj = { a: <span class="number">1</span> };
<span class="function">Object.freeze</span>(obj);
obj.a = <span class="number">2</span>; <span class="comment">// ❌ Ignored (or TypeError in strict mode)</span>
console.log(obj.a); <span class="comment">// 1</span>
</pre>
</div>
<div class="demo-box">
<h2>4. Interactive: Mutability Demo</h2>
<div class="state-display" id="stateDisplay">Original state</div>
<div style="margin: 10px 0;">
<button class="btn" onclick="demoPrimitive()">Primitive Reassign</button>
<button class="btn btn-success" onclick="demoObjectMutation()">Mutate Object</button>
<button class="btn btn-danger" onclick="demoFrozenObject()">Frozen Object</button>
<button class="btn" onclick="demoSharedReference()">Shared Reference</button>
<button class="btn" onclick="demoDeepFreeze()">Deep Freeze</button>
<button class="btn btn-danger" onclick="resetDemo()">Reset</button>
</div>
<div id="demoOutput"></div>
</div>
<div class="demo-box">
<h2>5. const vs Object.freeze()</h2>
<table>
<tr>
<th>Keyword / Method</th>
<th>Prevents Reassignment</th>
<th>Prevents Mutation</th>
</tr>
<tr>
<td><code>let</code></td>
<td>❌ No</td>
<td>❌ No</td>
</tr>
<tr>
<td><code>const</code></td>
<td>✅ Yes</td>
<td>❌ No</td>
</tr>
<tr>
<td><code>const + Object.freeze()</code></td>
<td>✅ Yes</td>
<td>✅ Yes (shallow)</td>
</tr>
<tr>
<td><code>const + deepFreeze()</code></td>
<td>✅ Yes</td>
<td>✅ Yes (deep)</td>
</tr>
</table>
</div>
<div class="demo-box">
<h2>6. Live Output — All Examples</h2>
<div id="output">Loading...</div>
</div>
<script>
// ============================================
// Mutability — Live Demo
// ============================================
let results = [];
// 1. Primitives are immutable
results.push('📌 Primitives Are Immutable:\n');
let x = 5;
let y = x;
y = 10;
results.push(' let x = 5; let y = x; y = 10;');
results.push(' x → ' + x + ' (unchanged!)');
results.push(' y → ' + y);
results.push('');
// 2. Objects are mutable
results.push('📌 Objects Are Mutable:\n');
let obj = { a: 1 };
results.push(' Before: ' + JSON.stringify(obj));
obj.a = 2;
results.push(' After obj.a = 2: ' + JSON.stringify(obj));
obj.b = 3;
results.push(' After obj.b = 3: ' + JSON.stringify(obj));
delete obj.a;
results.push(' After delete obj.a: ' + JSON.stringify(obj));
results.push('');
// 3. Shared references
results.push('📌 Shared References (⚠️ Pitfall):\n');
const obj1 = { name: 'Alice', score: 100 };
const obj2 = obj1; // Same reference!
obj2.score = 200;
results.push(' const obj1 = { name: "Alice", score: 100 };');
results.push(' const obj2 = obj1;');
results.push(' obj2.score = 200;');
results.push(' obj1.score → ' + obj1.score + ' (changed too!)');
results.push(' obj2.score → ' + obj2.score);
results.push(' ⚠️ obj1 and obj2 point to the SAME object!');
results.push('');
// 4. Copying objects (avoiding shared references)
results.push('📌 Copying Objects (Fix for Shared Refs):\n');
const original = { name: 'Alice', score: 100 };
const copy = { ...original }; // Shallow copy
copy.score = 999;
results.push(' const copy = { ...original };');
results.push(' copy.score = 999;');
results.push(' original.score → ' + original.score + ' (unchanged ✅)');
results.push(' copy.score → ' + copy.score);
results.push('');
// 5. const doesn't prevent mutation
results.push('📌 const Does NOT Prevent Mutation:\n');
const constObj = { a: 1 };
results.push(' const obj = { a: 1 };');
results.push(' obj.a = 2 → allowed!');
constObj.a = 2;
results.push(' obj.a → ' + constObj.a);
results.push(' ⚠️ const prevents REASSIGNMENT, not mutation');
results.push('');
// 6. Object.freeze()
results.push('📌 Object.freeze():\n');
const frozen = { a: 1 };
Object.freeze(frozen);
results.push(' const obj = { a: 1 };');
results.push(' Object.freeze(obj);');
results.push(' obj.a = 2 → ignored');
// Try to mutate (silently fails in non-strict mode)
frozen.a = 2;
results.push(' obj.a → ' + frozen.a + ' (unchanged ✅)');
results.push(' Object.isFrozen(obj) → ' + Object.isFrozen(frozen));
results.push('');
// 7. Shallow freeze limitation
results.push('📌 Object.freeze() Is Shallow:\n');
const shallowFrozen = {
name: 'Alice',
address: { city: 'NYC' }
};
Object.freeze(shallowFrozen);
shallowFrozen.name = 'Bob'; // ❌ Frozen
shallowFrozen.address.city = 'LA'; // ✅ Nested not frozen!
results.push(' obj.name = "Bob" → ' + shallowFrozen.name + ' (frozen)');
results.push(' obj.address.city = "LA" → ' + shallowFrozen.address.city + ' (nested not frozen!)');
results.push(' ⚠️ Object.freeze() is shallow — nested objects still mutable');
results.push('');
// 8. Deep freeze
results.push('📌 Deep Freeze (Recursive):\n');
function deepFreeze(obj) {
Object.freeze(obj);
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null && !Object.isFrozen(obj[key])) {
deepFreeze(obj[key]);
}
});
return obj;
}
const deepFrozen = deepFreeze({
name: 'Alice',
address: { city: 'NYC' }
});
deepFrozen.address.city = 'LA';
results.push(' After deepFreeze: obj.address.city = "LA" → ' + deepFrozen.address.city + ' (unchanged ✅)');
results.push('');
// 9. Arrays are mutable
results.push('📌 Arrays Are Mutable:\n');
const arr = [1, 2, 3];
results.push(' const arr = [1, 2, 3];');
arr.push(4);
arr[0] = 99;
results.push(' After push(4) and arr[0] = 99: [' + arr.join(', ') + ']');
results.push('');
// 10. Frozen array
results.push('📌 Frozen Array:\n');
const frozenArr = [1, 2, 3];
Object.freeze(frozenArr);
frozenArr.push(4); // Ignored
frozenArr[0] = 99; // Ignored
results.push(' After Object.freeze(arr):');
results.push(' arr.push(4) → ignored');
results.push(' arr[0] = 99 → ignored');
results.push(' arr → [' + frozenArr.join(', ') + '] (unchanged ✅)');
document.getElementById('output').textContent = results.join('\n');
// ============================================
// Interactive Demos
// ============================================
const stateDisplay = document.getElementById('stateDisplay');
const demoOutput = document.getElementById('demoOutput');
function demoPrimitive() {
let num = 5;
stateDisplay.textContent = 'let num = ' + num;
num = 10;
demoOutput.innerHTML = `
<p><strong>Primitive Reassignment:</strong></p>
<p>Original value: <code>5</code></p>
<p>After <code>num = 10</code>: <code>10</code></p>
<p style="color: #28a745;">✅ The original 5 is unchanged — a NEW value was created</p>
`;
stateDisplay.textContent = 'num = ' + num;
}
function demoObjectMutation() {
const obj = { count: 0 };
stateDisplay.textContent = 'Object: ' + JSON.stringify(obj);
setTimeout(() => {
obj.count = 99;
stateDisplay.textContent = 'Object: ' + JSON.stringify(obj);
demoOutput.innerHTML = `
<p><strong>Object Mutation:</strong></p>
<p>Before: <code>{ count: 0 }</code></p>
<p>After <code>obj.count = 99</code>: <code>${JSON.stringify(obj)}</code></p>
<p style="color: #dc3545;">⚠️ The original object was MUTATED</p>
`;
}, 500);
}
function demoFrozenObject() {
const obj = { count: 0 };
Object.freeze(obj);
obj.count = 99; // Silently ignored
stateDisplay.textContent = 'Frozen Object: ' + JSON.stringify(obj);
demoOutput.innerHTML = `
<p><strong>Frozen Object:</strong></p>
<p><code>Object.freeze(obj); obj.count = 99;</code></p>
<p>Result: <code>${JSON.stringify(obj)}</code></p>
<p style="color: #28a745;">✅ Object frozen — mutation ignored!</p>
<p><code>Object.isFrozen(obj) → ${Object.isFrozen(obj)}</code></p>
`;
}
function demoSharedReference() {
const original = { value: 100 };
const reference = original;
reference.value = 999;
stateDisplay.textContent = 'Shared Reference Demo';
demoOutput.innerHTML = `
<p><strong>Shared References:</strong></p>
<p><code>const original = { value: 100 };</code></p>
<p><code>const reference = original;</code></p>
<p><code>reference.value = 999;</code></p>
<p>original.value: <strong style="color: #dc3545;">${original.value}</strong></p>
<p>reference.value: <strong>${reference.value}</strong></p>
<p style="color: #dc3545;">⚠️ Both variables point to the SAME object!</p>
<p><strong>Fix:</strong> <code>const copy = { ...original };</code> (spread operator)</p>
`;
}
function demoDeepFreeze() {
function deepFreeze(obj) {
Object.freeze(obj);
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null && !Object.isFrozen(obj[key])) {
deepFreeze(obj[key]);
}
});
return obj;
}
const obj = deepFreeze({
name: 'Alice',
address: { city: 'NYC', zip: '10001' }
});
obj.name = 'Bob';
obj.address.city = 'LA';
obj.address.zip = '99999';
stateDisplay.textContent = 'Deep Frozen: ' + JSON.stringify(obj);
demoOutput.innerHTML = `
<p><strong>Deep Freeze:</strong></p>
<p><code>deepFreeze(obj);</code></p>
<p>Attempted mutations:</p>
<ul>
<li><code>obj.name = "Bob"</code> → ${obj.name}</li>
<li><code>obj.address.city = "LA"</code> → ${obj.address.city}</li>
<li><code>obj.address.zip = "99999"</code> → ${obj.address.zip}</li>
</ul>
<p style="color: #28a745;">✅ All nested properties are frozen!</p>
`;
}
function resetDemo() {
stateDisplay.textContent = 'Original state';
demoOutput.innerHTML = '';
}
</script>
</body>
</html>
Quick Reference
Primitives vs Objects
| Aspect | Primitives | Objects |
|---|---|---|
| Mutability | ❌ Immutable | ✅ Mutable |
| Storage | By value | By reference |
| Assignment | Copies the value | Copies the reference |
| Types | number, string, boolean, null, undefined, symbol, bigint | object, array, function |
Immutability Methods
| Method | Effect |
|---|---|
Object.freeze(obj) | Prevents add/remove/change (shallow) |
Object.seal(obj) | Prevents add/remove (allows change) |
Object.preventExtensions(obj) | Prevents adding new properties |
deepFreeze(obj) | Recursively freezes all nested objects |
const vs Object.freeze()
| Keyword | Reassignment | Mutation |
|---|---|---|
let | ✅ Allowed | ✅ Allowed |
const | ❌ Not allowed | ✅ Allowed |
const + Object.freeze() | ❌ Not allowed | ❌ Not allowed |
Best Practices
✅ Do This:
// Copy objects to avoid shared references
const copy = { ...original };
const arrCopy = [...originalArr];
// Use spread for immutable updates
const updated = { ...user, name: 'Bob' };
const newArr = [...arr, newItem];
// Use Object.freeze() for constants
const CONFIG = Object.freeze({
API_URL: 'https://api.example.com',
TIMEOUT: 5000
});
// Deep freeze for nested immutability
function deepFreeze(obj) {
Object.freeze(obj);
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
deepFreeze(obj[key]);
}
});
return obj;
}
// Use pure functions that return new objects
function updateUser(user, newName) {
return { ...user, name: newName };
}
❌ Don’t Do This:
// Don't assume const prevents mutation
const obj = { a: 1 };
obj.a = 2; // ⚠️ Works! const doesn't prevent this
// Don't share references accidentally
const a = { value: 1 };
const b = a; // Same reference!
b.value = 2; // ❌ Modifies a too!
// Don't expect Object.freeze() to be deep
const obj = { nested: { a: 1 } };
Object.freeze(obj);
obj.nested.a = 2; // ⚠️ Works! Nested not frozen
// Don't mutate function arguments
function process(arr) {
arr.push('new'); // ❌ Side effect!
return arr;
}
// Use: function process(arr) { return [...arr, 'new']; }
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Shared references | Mutating one affects another | Use spread { ...obj } |
const misconception | Thinks it’s immutable | Use Object.freeze() too |
| Shallow freeze | Nested still mutable | Use deepFreeze() |
| Mutating function args | Unexpected side effects | Return new objects |
Object.freeze() on arrays | Still allows reading | Intended behavior |
Mutability Visual
PRIMITIVES (Immutable) OBJECTS (Mutable)
───────────────────── ─────────────────
let x = 5; const obj = { a: 1 };
│ │
▼ ▼
┌───────┐ ┌───────────┐
│ 5 │ │ { a: 1 } │ ← Reference
└───────┘ └───────────┘
│ │
x = 10; (new value) obj.a = 2; (mutates)
│ │
▼ ▼
┌───────┐ ┌───────────┐
│ 10 │ │ { a: 2 } │ ← Same object!
└───────┘ └───────────┘
Original 5 still exists Original object modified
Pro Tip: Primitives are immutable — every “change” creates a new value. Objects are mutable by default — changing a property modifies the original. Use const to prevent reassignment, but remember it doesn’t prevent mutation! For true immutability, use Object.freeze() (shallow) or deepFreeze() (recursive). To avoid shared reference bugs, always copy objects with spread ({ ...obj }) or Object.assign({}, obj) before modifying. And in functional programming, prefer pure functions that return new objects instead of mutating existing ones!
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!