IMPLEMENTATION QUALITY / OBJECT DESIGN BRIEF

Immutability

An immutable object cannot change after construction.

IntermediatePhase 07 / Topic 3 of 8ResponsibilitiesCollaborationsExtensibility
01

Overview

An immutable object cannot change after construction. To 'modify' it, you create a new object with the changed value. Immutable objects are simpler to reason about (no hidden changes), inherently thread-safe (no synchronization needed), safe to share and cache, and make great map keys.

In practice: final/readonly fields, no setters, defensive copies of mutable inputs, and 'with' methods returning new instances. Java records, Kotlin data classes, TypeScript readonly types, and libraries like Immer or persistent collections make immutability convenient. Mutable state still exists in systems, but it is concentrated in a few well-controlled places.

A printed receipt

Once printed, a receipt never changes. If there is a correction, the store issues a new receipt. Anyone holding the original can trust it.

02

When to use it

  • Value objects: Money, Date ranges, Coordinates.
  • Data shared across threads.
  • Configuration and keys in maps or caches.
  • Event and message payloads.
03

Where it shows up in interviews

Thread safety by design

Recognize it when: shared data across threads.

  • Design a thread-safe config service
  • Design a concurrent cache
Value modeling

Recognize it when: money, time ranges, coordinates.

  • Design a booking system with time slots
  • Design Splitwise amounts
04

Where it is used in real software

java.time

LocalDate, Instant, and Duration are immutable; plusDays returns a new object.

String in Java and JavaScript

Strings are immutable, which allows interning and safe sharing.

React and Redux

State updates create new objects so changes are detected by reference comparison.

05

Key terms

Immutable object
State cannot change after construction.
Defensive copy
Copy mutable inputs and outputs.
Wither
withX() returns a modified copy.
Persistent data structure
Shares structure between versions efficiently.
Shallow immutability
Fields cannot be reassigned, but referenced objects may still change.
06

How it works, step by step

  1. 1
    Make fields final/readonly

    Assign once in the constructor.

  2. 2
    Remove setters

    Provide withX() methods instead.

  3. 3
    Copy mutable inputs

    List.copyOf, spread operators.

  4. 4
    Return unmodifiable views or copies

    Never internal mutable collections.

  5. 5
    Make the class final

    Prevent subclasses adding mutability.

07

Mutable vs immutable date range

A booking holds a DateRange that another piece of code 'adjusts'

Step 1 / 3
ScenarioMutable DateRangeImmutable DateRange
Code extends range for a previewBooking silently changesNew object; booking unchanged
Two threads read and writeRace conditionsNo synchronization needed
Used as a map keyKey changes, lookup breaksHash stays stable

NOWScenario: Code extends range for a preview | Mutable DateRange: Booking silently changes | Immutable DateRange: New object; booking unchanged

Immutability removes a whole class of aliasing and concurrency bugs.

08

Implementation

public record DateRange(LocalDate start, LocalDate end) {    public DateRange {        if (end.isBefore(start)) throw new IllegalArgumentException("end before start");    }    public long nights() { return ChronoUnit.DAYS.between(start, end); }    public boolean overlaps(DateRange o) { return start.isBefore(o.end) && o.start.isBefore(end); }    public DateRange extendBy(int days) { return new DateRange(start, end.plusDays(days)); } // new object} public final class Cart {    private final List<String> skus;    public Cart(List<String> skus) { this.skus = List.copyOf(skus); } // defensive copy    public List<String> skus() { return skus; }                       // already unmodifiable    public Cart add(String sku) {        var next = new ArrayList<>(skus);        next.add(sku);        return new Cart(next);    }}
09

Complexity and performance

UpdateO(copied size)

Persistent structures reduce this.

SynchronizationNone

Immutable data is thread-safe.

10

Trade-offs

Safety vs allocation

Creating new objects on every change costs memory and GC for large, frequently updated structures.

Ergonomics

Deep updates of nested immutable data are verbose without helpers (Immer, lenses).

11

Variants and related techniques

Builders for immutable objects

Mutable builder, immutable product.

Copy-on-write collections

CopyOnWriteArrayList for read-mostly concurrent data.

12

Common mistakes

  • final fields referencing mutable lists.

    Fix: Copy into unmodifiable collections.

  • readonly in TypeScript assumed to be deep.

    Fix: readonly is compile-time and shallow; use Readonly types recursively or freeze.

13

Interview questions

Why are immutable objects thread-safe?

Their state never changes after construction, so threads can share them without locks and never see partially updated state.

How do you make a Java class immutable?

Make it final, fields private final, no setters, validate in the constructor, defensively copy mutable inputs, and return unmodifiable or copied collections. Records cover most of this.

14

Practice problems

ProblemDifficultyWhat it trains
Make an Order immutable with withersEasyCopy on change.
Refactor a shared mutable config to immutable snapshotsMediumConcurrency.