PROGRAMMING-CONCEPTS

Immutability: Definition, Purpose, and Examples

Immutability means that once a value is created, it cannot be changed. Instead of modifying the original value, you create a new one with the updated data. This idea is central in functional programming but also plays a major role in modern JavaScript development (especially React), Python data handling, and Swift’s safety-oriented model.

Immutable data leads to more predictable programs. When values don’t change in place, you avoid side effects, make debugging easier, and reduce errors caused by accidental mutations.


What Immutability Really Means

Mutable data changes itself.

Immutable data creates a new version of itself.

This distinction affects:

  • how you design functions
  • how you share data between parts of a program
  • how UI updates work in frameworks like React
  • how concurrency issues are prevented

Beginners often overlook immutability until a hard-to-find bug appears — usually caused by two parts of the code unintentionally modifying the same object.


Mutability vs. Immutability at a Glance

  • Mutable values: arrays, dictionaries, most objects in JavaScript and Python
  • Immutable values: strings, numbers, tuples (Python), enums (Swift), structs (Swift under certain conditions)

Even when a language allows mutation, developers often choose immutability because it leads to clearer, safer code.


Immutability in JavaScript and TypeScript

JavaScript’s primitive values (strings, numbers, booleans, null, undefined) are immutable.

Objects and arrays are mutable by default, but modern JavaScript encourages treating them immutably — especially in frameworks like React.


Creating New Arrays Instead of Mutating Them

const ratings = [3, 4, 5];

// immutable update
const updated = [...ratings, 4];

Instead of calling ratings.push(4) (mutable), this version creates a new array.

This prevents accidental changes to shared references.


Updating Object Properties Immutably

const config = { mode: "standard", delay: 300 };

const newConfig = {
  ...config,
  delay: 500,
};

Immutability here ensures you don’t unexpectedly modify another part of the code holding the same object reference.


Immutability in TypeScript Models

interface Position {
  readonly x: number;
  readonly y: number;
}

const start: Position = { x: 12, y: 30 };
// start.x = 40;  // ❌ cannot assign to readonly property

readonly helps enforce immutability at compile time.


Immutability in Python

Python treats some built-in types as immutable: strings, tuples, integers, floats, and frozensets.

Lists, dictionaries, and most objects, however, remain mutable.


Immutable Python Types in Action

name = "River"
# name[0] = "G"  # ❌ strings cannot be modified

coords = (10, 20)
# coords[0] = 50  # ❌ tuples are immutable

To “modify” immutable values, you must create new ones:

new_name = "G" + name[1:]

Avoiding Shared Mutable References

Mutable types can introduce hard-to-track mutations.

template = {"active": True}
copy = template.copy()

copy["active"] = False

Using .copy() prevents changes to template.

This pattern mimics immutability in situations where Python doesn’t enforce it.


Immutability in Swift

Swift encourages immutability for clarity and safety.

The let keyword creates constants that cannot change after initialization.


Value Types vs. Reference Types

Swift structures (struct) are value types.

They produce a new copy on assignment or mutation.

struct Profile {
    var nickname: String
}

let p = Profile(nickname: "Skye")
// p.nickname = "Wave" // ❌ cannot modify a let constant

Using var instead of let makes the instance mutable.

This selective control promotes intentional design.


Enforcing Immutable Behavior

You can choose immutable properties even in mutable structs:

struct Ticket {
    let code: String
    var used: Bool
}

The code can never change; used can.


Why Developers Embrace Immutability

Even in languages where mutation is possible, immutability offers benefits:

  • predictable data flow
  • easier debugging
  • safer async and concurrent operations
  • clearer intent
  • fewer accidental changes to shared references

UI frameworks like React rely heavily on immutable updates to detect state changes efficiently.


Real-World Examples

Fresh, practical scenarios showing why immutability matters.


Example 1: React State Updates

function Cart() {
  const [items, setItems] = useState(["pen", "notebook"]);

  const addItem = (newItem) => {
    setItems(prev => [...prev, newItem]);  // immutable update
  };

  return <button onClick={() => addItem("marker")}>Add</button>;
}

React expects new arrays/objects to trigger rerenders.

Mutating prev in place would cause the UI to stay stuck.


Example 2: Python Log Builder

def add_line(log, text):
    return log + [text]  # creates a new list

Instead of log.append(text) (mutable), the function returns a new list.

This pattern prevents one part of the program from unintentionally modifying another’s data.


Example 3: Swift Model Transformation

struct Settings {
    var volume: Int
}

let base = Settings(volume: 60)
let louder = Settings(volume: base.volume + 20)

Each instance is separate.

Changing louder doesn’t affect base, reinforcing safe data handling.


Structural Sharing (A Modern Optimization)

Immutability can be expensive if every update creates a full copy.

To improve performance, some languages and libraries use structural sharing:

  • only the changed parts are copied
  • the rest is shared with the original
  • both versions appear immutable to the developer

Frameworks like Immer (used in Redux) use this approach behind the scenes.


Common Mistakes with Immutability

Assuming arrays or objects are immutable in JavaScript

They are not — you must treat them immutably manually.

Confusing “constant” with “immutable”

In JavaScript:

const settings = { mode: "fast" };
settings.mode = "slow"; // ✔ allowed (object is still mutable)

const only prevents reassignment, not modification.

Mutating nested structures accidentally

A shallow copy isn’t always enough:

const state = { options: { speed: "fast" } };
const copy = { ...state };

copy.options.speed = "slow"; // mutates both

Deep structures require deeper cloning or libraries that enforce immutability.

Overusing immutability for large data

Sometimes mutation is faster and safe if the value is not shared widely.


Best Practices

  • Prefer new objects rather than mutating existing ones
  • Use language tools (let, readonly, tuples, frozen types) to enforce immutability
  • Deep clone nested structures when needed
  • In UI frameworks, use immutable updates to trigger proper rendering
  • Document clearly if a function mutates data or returns new data

Clear expectations reduce confusion across a team.


Summary

Immutability ensures that values never change once created.

JavaScript primitives, Python strings, and Swift constants all follow this principle.

Arrays, dictionaries, and objects can be treated immutably by returning new versions instead of modifying the originals.

Immutability leads to safer, more predictable code—especially when managing UI state, working with shared data, or building concurrent systems.

Once you become comfortable working immutably, your programs become easier to reason about, harder to break, and simpler to debug.

Learn to Code for Free
Start learning now
button icon
To advance beyond this tutorial and learn to code by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

Reach your coding goals faster