← Back to Articles         
Immutable and Read-Only Collections

Immutable and Read-Only Collections

Published on 12 Oct 2026     7 min read C# Collections & Generics
Immutable and Read-Only Collections

In C#, collections can be mutable, read-only, or immutable. Understanding their differences is important when designing thread-safe applications, exposing data through APIs, and preventing accidental modifications.

1. What is the difference between mutable and immutable collections?

What is a Mutable Collection?

A mutable collection allows you to add, remove, or update elements after the collection is created.

Examples:

  • List<T>

  • Dictionary<TKey, TValue>

  • HashSet<T>

What is an Immutable Collection?

An immutable collection cannot be modified after creation. Operations such as Add(), Remove(), and SetItem() return a new collection value instead of changing the existing collection.

Examples:

  • ImmutableList<T>

  • ImmutableDictionary<TKey, TValue>

  • ImmutableHashSet<T>

Diagram

Mutable Collection
------------------

List<int> numbers = [10, 20, 30]

         Add(40)
            |
            v
Original List = [10, 20, 30, 40]

Same collection modified.


Immutable Collection
--------------------

ImmutableList<int> numbers = [10, 20, 30]

         Add(40)
            |
            v
Original = [10, 20, 30]
New      = [10, 20, 30, 40]

Original collection remains unchanged.

Example Program

using System;
using System.Collections.Generic;
using System.Collections.Immutable;

class Program
{
    static void Main()
    {
        // Mutable collection
        List<int> numbers = new() { 10, 20, 30 };

        numbers.Add(40);

        Console.WriteLine(string.Join(", ", numbers));

        // Immutable collection
        ImmutableList<int> immutable =
            ImmutableList.Create(10, 20, 30);

        ImmutableList<int> updated = immutable.Add(40);

        Console.WriteLine(string.Join(", ", immutable));
        Console.WriteLine(string.Join(", ", updated));
    }
}

Output:

10, 20, 30, 40
10, 20, 30
10, 20, 30, 40

Explanation

  • List<T>.Add() modifies the existing collection.

  • ImmutableList<T>.Add() returns a new immutable list.

  • The original immutable collection remains unchanged.

  • Immutable collections can reuse internal data structures, so they do not necessarily copy every element.

Comparison

Feature

Mutable

Immutable

Modification

Allowed

Not allowed in place

Add/Remove

Modifies existing collection

Returns new collection

Thread safety

Requires synchronization for concurrent modifications

Safe for concurrent reads

Performance

Generally faster for frequent updates

Additional cost for updates

Examples

List<T>, Dictionary

ImmutableList<T>

Key Points

  • Mutable: Existing collection can change.

  • Immutable: Existing collection cannot change.

  • Immutable collections are useful when multiple threads share data.

  • Immutability applies to the collection structure, not automatically to mutable objects stored inside it.

  • Prefer mutable collections for frequent modifications and immutable collections for stable shared data.

 

2. What is the difference between ReadOnlyCollection<T> and ImmutableList<T>?

What is ReadOnlyCollection<T>?

ReadOnlyCollection<T> provides a read-only wrapper around an existing list.

Consumers cannot directly add, remove, or replace elements through the wrapper.

However, if the original list changes, those changes are reflected in the read-only collection.

Namespace:

using System.Collections.ObjectModel;

What is ImmutableList<T>?

ImmutableList<T> is an immutable collection whose contents cannot be changed after creation.

Methods such as Add() and Remove() return new immutable lists.

Namespace:

using System.Collections.Immutable;

Diagram

ReadOnlyCollection<T>
---------------------

Original List
[10, 20, 30]
      |
      | ReadOnlyCollection wrapper
      v
ReadOnly View
[10, 20, 30]

Original List.Add(40)
      |
      v
Original List = [10, 20, 30, 40]
ReadOnly View = [10, 20, 30, 40]

Changes are visible.


ImmutableList<T>
----------------

Original ImmutableList
[10, 20, 30]
      |
      | Add(40)
      v
New ImmutableList
[10, 20, 30, 40]

Original remains unchanged.

Example Program

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Immutable;

class Program
{
    static void Main()
    {
        // ReadOnlyCollection
        List<int> numbers = new() { 10, 20, 30 };

        ReadOnlyCollection<int> readOnly =
            numbers.AsReadOnly();

        numbers.Add(40);

        Console.WriteLine("ReadOnly:");
        Console.WriteLine(string.Join(", ", readOnly));

        // ImmutableList
        ImmutableList<int> immutable =
            ImmutableList.Create(10, 20, 30);

        ImmutableList<int> updated = immutable.Add(40);

        Console.WriteLine("Original Immutable:");
        Console.WriteLine(string.Join(", ", immutable));

        Console.WriteLine("Updated Immutable:");
        Console.WriteLine(string.Join(", ", updated));
    }
}

Output:

ReadOnly:
10, 20, 30, 40

Original Immutable:
10, 20, 30

Updated Immutable:
10, 20, 30, 40

Explanation

ReadOnlyCollection:

ReadOnlyCollection<int> readOnly = numbers.AsReadOnly();
  • Creates a read-only view of numbers.

  • Does not create an independent copy.

  • Changes to numbers are reflected in readOnly.

ImmutableList:

ImmutableList<int> updated = immutable.Add(40);
  • Creates a new immutable list value.

  • Does not change immutable.

  • The original list remains unchanged.

Comparison

Feature

ReadOnlyCollection<T>

ImmutableList<T>

Type

Read-only wrapper

Immutable collection

Direct modification

Not allowed

Not allowed

Underlying data can change

Yes

No

Reflects changes to original list

Yes

No

Add() operation

Not supported publicly

Returns new list

Thread safety

Depends on underlying list

Safe for concurrent reads

Best use

Exposing a list without write access

Sharing stable collection data

Real-Time Scenario

Suppose an ASP.NET Core application maintains a list of supported countries.

ReadOnlyCollection:

private readonly List<string> _countries =
    new() { "India", "USA" };

public IReadOnlyList<string> Countries =>
    _countries.AsReadOnly();

Other components cannot modify the list through the returned interface, but the owning class can still change _countries.

ImmutableList:

private ImmutableList<string> _countries =
    ImmutableList.Create("India", "USA");

public ImmutableList<string> Countries => _countries;

Callers cannot change the published list. The owning class can publish a new list value when necessary.

Key Points

  • ReadOnlyCollection<T> is a wrapper, not an immutable snapshot.

  • ImmutableList<T> guarantees that its collection contents remain unchanged.

  • ReadOnlyCollection<T> reflects modifications to its underlying list.

  • ImmutableList<T> returns a new collection when updated.

  • Use ImmutableList<T> when collection immutability is required.

 

3. What is the difference between IReadOnlyList<T> and ImmutableList<T>?

What is IReadOnlyList<T>?

IReadOnlyList<T> is an interface that provides read-only access to an ordered collection.

It supports:

  • Accessing elements using an index.

  • Reading the number of elements using Count.

  • Iterating using foreach.

It does not expose methods to add, remove, or replace elements.

However, it does not guarantee that the underlying collection is immutable.

What is ImmutableList<T>?

ImmutableList<T> is a concrete immutable collection implementation.

It implements IReadOnlyList<T> and provides methods that produce new immutable collection values.

Diagram

             IReadOnlyList<T>
                    |
          Read-only interface
                    |
          -------------------
          |                 |
          v                 v
       List<T>       ImmutableList<T>
          |                 |
       Mutable          Immutable
          |                 |
   Can change through   Cannot change
   another reference    existing contents

Example Program

using System;
using System.Collections.Generic;
using System.Collections.Immutable;

class Program
{
    static void Main()
    {
        // IReadOnlyList backed by mutable List
        List<int> numbers = new() { 10, 20, 30 };

        IReadOnlyList<int> readOnly = numbers;

        numbers.Add(40);

        Console.WriteLine("IReadOnlyList:");
        Console.WriteLine(string.Join(", ", readOnly));

        // ImmutableList
        ImmutableList<int> immutable =
            ImmutableList.Create(10, 20, 30);

        ImmutableList<int> updated = immutable.Add(40);

        Console.WriteLine("ImmutableList:");
        Console.WriteLine(string.Join(", ", immutable));
    }
}

Output:

IReadOnlyList:
10, 20, 30, 40

ImmutableList:
10, 20, 30

Explanation

IReadOnlyList:

IReadOnlyList<int> readOnly = numbers;
  • readOnly references the existing mutable list.

  • You cannot call readOnly.Add(40).

  • But numbers.Add(40) is allowed.

  • Therefore, the changes are visible through readOnly.

ImmutableList:

ImmutableList<int> updated = immutable.Add(40);
  • immutable remains unchanged.

  • updated contains the additional element.

  • The collection guarantees immutability of its structure.

Comparison

Feature

IReadOnlyList<T>

ImmutableList<T>

Type

Interface

Concrete class

Read-only access

Yes

Yes

Guarantees immutability

No

Yes

Underlying collection

May be mutable

Immutable

Index access

Yes

Yes

Add()

Not available through interface

Returns new list

Can reference List<T>

Yes

No

Primary purpose

Read-only API contract

Immutable data storage

Real-Time Scenario

In Clean Architecture, suppose an application service returns a collection of employees.

Using IReadOnlyList:

public IReadOnlyList<Employee> GetEmployees()
{
    return _employees.AsReadOnly();
}

This exposes read-only access, but the service can still modify _employees.

Using ImmutableList:

public ImmutableList<Employee> GetEmployees()
{
    return _employees.ToImmutableList();
}

This returns an immutable collection snapshot. Later changes to the original list will not affect the returned collection.

Note that if Employee is a mutable class, its properties may still be modified. Collection immutability does not make the employee objects immutable.

Key Points

  • IReadOnlyList<T> is an interface, not a collection implementation.

  • It restricts modifications through that interface.

  • ImmutableList<T> is an actual immutable collection.

  • ImmutableList<T> implements IReadOnlyList<T>.

  • Use IReadOnlyList<T> for flexible read-only method contracts.

  • Use ImmutableList<T> when you need an immutable collection guarantee.

 

Final Comparison: All Three Collections

Feature

List<T>

ReadOnlyCollection<T>

ImmutableList<T>

Mutable

Yes

No, through wrapper

No

Underlying data can change

Yes

Yes

No

Add/Remove directly

Yes

No

Returns new list

Reflects external changes

Yes

Yes

No

Safe for concurrent reads without writes

Yes

Yes

Yes

Best use

Frequent updates

Read-only view

Immutable shared data

Important Interview Questions and Answers

Q1. Is IReadOnlyList<T> immutable?

No. It provides read-only access but does not guarantee immutability.

Q2. Can ReadOnlyCollection<T> change?

Yes. If the underlying list changes, the read-only wrapper reflects those changes.

Q3. Does ImmutableList<T>.Add() modify the original list?

No. It returns a new immutable list.

Q4. Are immutable collections thread-safe?

Immutable collection instances are safe for concurrent reads because their structure cannot change. However, mutable elements and concurrent replacement of shared references may still require synchronization.

Q5. Which collection should be used in enterprise applications?

  • List<T> — Frequent additions, removals, and updates.

  • ReadOnlyCollection<T> — Exposing an internally managed list without direct write access.

  • IReadOnlyList<T> — Defining read-only service or API contracts.

  • ImmutableList<T> — Sharing collection data that must not change.

Interview takeaway: Read-only means you cannot modify the collection through the given interface or wrapper. Immutable means the existing collection value itself cannot be changed.

← Back to Articles