Mutable vs. Immutable
C# / .NET Interview Notes
1. Big Picture
In C#, every type is either mutable (can change after creation) or immutable (cannot change after creation). Understanding this distinction is fundamental to writing correct, thread-safe, and predictable code.
2. Value Types vs Reference Types
C# has two categories of types, and mutability interacts with both:
Value Types (struct, enum, primitive)
Stored directly on the stack (or inline in a containing object). Copied by value — assigning creates a new copy.
int a = 5;
int b = a; // b is a COPY of a
b = 10; // a is still 5
Console.WriteLine(a); // 5
Console.WriteLine(b); // 10
Reference Types (class, interface, delegate, array)
Stored on the heap. Variables hold a reference (pointer) to the data. Assigning copies the reference, not the data.
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1; // list2 points to SAME list
list2.Add(4);
Console.WriteLine(list1.Count); // 4 — list1 changed too!
Console.WriteLine(list2.Count); // 4
3. Immutable Types in C#
string
The most famous immutable type. Every "modification" creates a new string:
string s = "hello";
s = s + " world"; // NEW string object created
s.ToUpper(); // returns NEW string, s unchanged
Console.WriteLine(s); // "hello" — unchanged!
s.ToUpper() does NOT modify s. You must assign: s = s.ToUpper().
All primitive types
int, double, bool, char, DateTime, etc. are all immutable. Operations return new values.
record
Reference type with immutable-by-default semantics and value-based equality:
record Person(string Name, int Age);
var p1 = new Person("Alice", 30);
var p2 = p1 with { Age = 31 }; // NEW record, p1 unchanged
Console.WriteLine(p1.Age); // 30
Console.WriteLine(p2.Age); // 31
4. Mutable Types in C#
List<T>
var list = new List<int> { 1, 2, 3 };
list.Add(4); // mutates in place
list.RemoveAt(0); // mutates in place
list[0] = 99; // mutates in place
StringBuilder
Mutable alternative to string for heavy manipulation:
var sb = new StringBuilder("hello");
sb.Append(" world"); // mutates in place
sb.Replace("world", "there");
Console.WriteLine(sb.ToString()); // "hello there"
Class instances
class Counter { public int Count { get; set; } }
var c = new Counter { Count = 0 };
c.Count++; // mutates the object
5. Why Immutability Matters
Thread Safety
Immutable objects are inherently thread-safe — no locks needed because nothing can change.
Predictability
No side effects from unexpected mutations. Easier to reason about code.
Hash Code Stability
Immutable objects can cache their hash code. Mutable objects in hash collections cause bugs:
// DANGER: mutable key in HashSet
var dict = new Dictionary<StringBuilder, int>();
var key = new StringBuilder("hello");
dict[key] = 1;
key.Append(" world"); // hash code changes — key is now lost!
6. readonly and init
readonly fields
class Config
{
public readonly string Name;
public Config(string name) { Name = name; } // set once in constructor
}
init-only setters (C# 9+)
class Person
{
public string Name { get; init; } // set at creation, then frozen
}
var p = new Person { Name = "Alice" };
// p.Name = "Bob"; // compile error!
7. Defensive Copying
When you must expose mutable data, return a copy to protect internal state:
class Team
{
private List<string> _members = new();
public IReadOnlyList<string> Members => _members.AsReadOnly();
public void AddMember(string name) => _members.Add(name);
}
8. Common Interview Questions
Q: Why is string immutable?
Security (no tampering after creation), thread safety, hash code caching, and string interning (memory optimization).
Q: What is the difference between readonly and const?
const is compile-time constant (must be set at declaration). readonly is runtime constant (can be set in constructor).
Q: How do you make a class immutable?
Make all fields readonly, use get-only properties, don't expose mutable collections, and return copies of any mutable data.
9. One-Line Interview Answer
Mutable types can be changed after creation (List, StringBuilder, class instances), while immutable types cannot (string, int, record). Immutability provides thread safety, predictability, and hash code stability — and C# offers readonly, init-only setters, and records to enforce it.