|

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 x originally points to the value 5
  • Assigning x = 10 makes x point to a new value 10
  • The original 5 is 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:

  • obj points to an object in memory
  • obj.a = 2 modifies 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:

TypeExampleMutable?
number42, 3.14❌ Immutable
string'hello'❌ Immutable
booleantrue, false❌ Immutable
nullnull❌ Immutable
undefinedundefined❌ Immutable
symbolSymbol('id')❌ Immutable
bigint123n❌ 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 x is reassigned — but the original value 5 is 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:

OperationEffect
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:

KeywordReassignmentMutation
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

AspectPrimitivesObjects
Mutability❌ Immutable✅ Mutable
StorageBy valueBy reference
AssignmentCopies the valueCopies the reference
Typesnumber, string, boolean, null, undefined, symbol, bigintobject, array, function

Immutability Methods

MethodEffect
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()

KeywordReassignmentMutation
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

PitfallProblemSolution
Shared referencesMutating one affects anotherUse spread { ...obj }
const misconceptionThinks it’s immutableUse Object.freeze() too
Shallow freezeNested still mutableUse deepFreeze()
Mutating function argsUnexpected side effectsReturn new objects
Object.freeze() on arraysStill allows readingIntended 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!