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 TypesReference Types
Stored inStack (usually)Heap
Examplesint, bool, double, DateTime, struct, enumclass, string, array, List<T>, delegate, record
Default value0, false, etc.null
AssignmentCopies the valueCopies the reference (pointer)
EqualityCompares 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

KeywordWhat it doesDirection
refPass the original variable (in/out)Both directions
outPass the original variable (must assign)Output only
inPass a read-only referenceInput only
void GetValues(out int sum, out int count) {
    sum = 10;    // Must assign
    count = 5;   // Must assign
}

Quick Reference Table

ScenarioCan 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 property

Immutable β€” 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#

TypeImmutable?Notes
stringβœ… YesEvery operation returns a new string
int, bool, DateTimeβœ… YesValue types β€” β€œchanging” replaces the value
record with initβœ… YesCan only set during initialization
ReadOnlyCollection<T>βœ… YesCannot add/remove items
StringBuilder❌ NoDesigned for mutation
List<T>, Dictionary<K,V>❌ NoFully mutable
Array❌ NoCan change elements, but can’t resize
class (yours)❌ UsuallyMutable 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?

ScenarioMutable ProblemImmutable Fix
ConcurrencyTwo threads modify same object β†’ race conditionThread-safe: nobody can change it
CachingSomeone modifies cached object β†’ corrupted cacheSafe to cache and share
DebuggingObject changes unexpectedly β†’ β€œwho changed my data?”Object always stays the same
DictionariesObject used as key gets its hash code changed β†’ key lostSafe 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 elsewhere

Q: β€œ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: