C# Fundamentals β Value, Reference, Mutability
Core concepts that trip up even experienced developers. Master these and youβll avoid a whole class of bugs.
1. Value Types vs Reference Types
The Memory Difference
| Value Types | Reference Types | |
|---|---|---|
| Stored in | Stack (usually) | Heap |
| Examples | int, bool, double, DateTime, struct, enum | class, string, array, List<T>, delegate, record |
| Default value | 0, false, etc. | null |
| Assignment | Copies the value | Copies the reference (pointer) |
| Equality | Compares values (a == b means same data) | Compares references (a == b means same object, unless Equals is overridden) |
// Value Type
int a = 5;
int b = a; // b gets a COPY of 5
a = 10;
Console.WriteLine(b); // Still 5 β independent!
// Reference Type
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1; // list2 points to the SAME list
list1.Add(4);
Console.WriteLine(list2.Count); // 4 β same object!π§ Key insight: Think of reference type variables as remote controls. When you assign b = a, youβre not giving b a new TV β youβre giving them a second remote that controls the same TV.
Nullability
int x = null; // β Compile error β value types can't be null (normally)
int? y = null; // β
Nullable<int> β special wrapper for value types
string s = null; // β
Reference types can be null by default
string? t = null; // β
With nullable enabled, same thing (warns if not handled)2. Pass by Value vs Pass by Reference
This is the most misunderstood concept in C#. Letβs be precise.
Value Types β Pass by Value (Default)
void Update(int x) {
x = 10; // Only changes the LOCAL copy
}
int a = 5;
Update(a);
Console.WriteLine(a); // 5 β unchanged!A copy of the value is passed. The original variable is untouched.
Reference Types β Pass by Value (Default)
This is where everyone gets confused. The reference itself is passed by value.
void ModifyObject(Person p) {
p.Name = "New"; // β
Modifies the original object
}
void ReassignObject(Person p) {
p = new Person("Different"); // β Only changes LOCAL copy of the reference
}
var person = new Person("Original");
ModifyObject(person);
Console.WriteLine(person.Name); // "New" β
ReassignObject(person);
Console.WriteLine(person.Name); // Still "New" β the reassignment didn't work!π§ The mental model:
Before ModifyObject():
person βββΆ { Name: "Original" }
p (copy) βββΆ { Name: "Original" } β Same object, different remote
After ModifyObject():
person βββΆ { Name: "New" } β Changed via the remote
p (copy) βββΆ { Name: "New" }
After ReassignObject():
person βββΆ { Name: "New" } β Still points to the same object
p (copy) βββΆ { Name: "Different" } β p now points to a NEW object
but person doesn't know!
Pass by Reference β The ref Keyword (Both Types)
void UpdateRef(ref int x) {
x = 10; // β
Changes the ORIGINAL variable
}
void ReassignRef(ref Person p) {
p = new Person("Different"); // β
Changes the ORIGINAL reference
}
int a = 5;
UpdateRef(ref a);
Console.WriteLine(a); // 10 β the original was changed!
var person = new Person("Original");
ReassignRef(ref person);
Console.WriteLine(person.Name); // "Different" β the original reference was changed!ref passes the actual variable slot β not a copy. Think of it as giving someone the address of your garage instead of a copy of your car keys.
Other Modifiers
| Keyword | What it does | Direction |
|---|---|---|
ref | Pass the original variable (in/out) | Both directions |
out | Pass the original variable (must assign) | Output only |
in | Pass a read-only reference | Input only |
void GetValues(out int sum, out int count) {
sum = 10; // Must assign
count = 5; // Must assign
}Quick Reference Table
| Scenario | Can modify the object? | Can reassign the variable? |
|---|---|---|
| Value type (no ref) | N/A (itβs a copy) | β Only local |
Value type (ref) | N/A | β Original |
| Reference type (no ref) | β Yes | β Only local |
Reference type (ref) | β Yes | β Original |
3. Mutable vs Immutable
Mutable β Can Change
Most C# objects are mutable by default:
var list = new List<int> { 1, 2, 3 };
list.Add(4); // β
Same list, new item
list[0] = 99; // β
Same list, changed value
var person = new Person();
person.Name = "New"; // β
Same object, changed propertyImmutable β Cannot Change
Once created, the objectβs state never changes. Any operation that βchangesβ it creates a new object.
// string is immutable
string s = "Hello";
string s2 = s.ToUpper(); // β
Creates NEW string "HELLO"
Console.WriteLine(s); // Still "Hello" β original unchanged!
// Every "change" to a string creates a new string in memory
string result = "";
for (int i = 0; i < 1000; i++) {
result += i.ToString(); // β Creates 1000 temporary strings!
}
// Use StringBuilder instead:
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.Append(i); // β
Modifies the SAME buffer
}Immutable Types in C#
| Type | Immutable? | Notes |
|---|---|---|
string | β Yes | Every operation returns a new string |
int, bool, DateTime | β Yes | Value types β βchangingβ replaces the value |
record with init | β Yes | Can only set during initialization |
ReadOnlyCollection<T> | β Yes | Cannot add/remove items |
StringBuilder | β No | Designed for mutation |
List<T>, Dictionary<K,V> | β No | Fully mutable |
Array | β No | Can change elements, but canβt resize |
class (yours) | β Usually | Mutable by default unless you design it otherwise |
Creating Immutable Types
// 1. record with init (C# 9+)
public record Person(string Name, int Age);
// Usage: var p = new Person("Alice", 30);
// Cannot: p.Name = "Bob"; β
// 2. readonly struct
public readonly struct Point {
public int X { get; }
public int Y { get; }
public Point(int x, int y) { X = x; Y = y; }
}
// 3. Class with private setters
public class Configuration {
public string ConnectionString { get; } // No setter β can only set in constructor
public Configuration(string cs) {
ConnectionString = cs;
}
}Why Does Mutability Matter?
| Scenario | Mutable Problem | Immutable Fix |
|---|---|---|
| Concurrency | Two threads modify same object β race condition | Thread-safe: nobody can change it |
| Caching | Someone modifies cached object β corrupted cache | Safe to cache and share |
| Debugging | Object changes unexpectedly β βwho changed my data?β | Object always stays the same |
| Dictionaries | Object used as key gets its hash code changed β key lost | Safe to use as dictionary key |
4. Common Interview Questions
Q: βDoes string behave like a value type or reference type?β
Answer: string is a reference type that behaves like a value type. The reference is on the heap, but its immutability makes it act like a value β every βchangeβ creates a new instance.
string a = "Hello";
string b = a; // b points to the SAME "Hello"
a += " World"; // Creates NEW string "Hello World"
Console.WriteLine(b); // Still "Hello" β a now points elsewhereQ: βWhat happens when you pass List<T> to a method?β
Answer: The reference is passed by value. The method gets a copy of the pointer, so it can modify the original listβs contents (add/remove/change items) but cannot reassign the original variable to a new list.
void Evil(List<int> list) {
list.Clear(); // β
Original list is now empty!
list = new List<int> { 99 }; // β Only local, original unchanged
}Q: βWhy is StringBuilder faster than string concatenation?β
Answer: string is immutable. Every += creates a new string object with the combined text, copying both strings into new memory. Thatβs O(nΒ²) time and creates many temporary objects for GC. StringBuilder maintains an internal mutable buffer (like a List<char>) and only creates the final string once, giving O(n) performance.
π Cheat Sheet
βββββββββββββββββββββββββββββββββββββββ
β Passed to Method β
ββββββββββββββββ¬βββββββββββββββββββββββ€
β Without ref β With ref β
βββββββββββββββββΌβββββββββββββββΌβββββββββββββββββββββββ€
β Value Type β Copy of val β Original variable β
β (int, struct) β Can't modify β Can modify directly β
βββββββββββββββββΌβββββββββββββββΌβββββββββββββββββββββββ€
β Reference Type β Copy of ref β Original reference β
β (class, list) β Can modify β Can modify + REASSIGN β
β β object β
β object + variable β
β
β β Can't REASSIGN β β β
βββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
Mutable: stringBuilder, List, Dictionary, Array, class
Immutable: string, int, bool, DateTime, record (init), ReadOnlyCollection
Related: