How are strings passed in .NET?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
One of the most common sources of confusion in C# is how strings behave when passed to methods. Strings are reference types, so you might expect them to behave like other objects, but their immutability changes everything. Understanding this distinction is essential because it affects whether your methods can modify the caller's data and how you should design APIs that accept string parameters.
Strings Are Reference Types, but Immutable
In .NET, System.String is a class, which makes it a reference type. A string variable holds a reference (a pointer) to an object on the heap, not the string data itself. However, unlike most reference types, strings are immutable. Once a string object is created, its contents can never change.
This matters for parameter passing because even though you pass a reference to a string, no one can mutate the original string object through that reference. Any operation that appears to modify the string actually creates a brand new string.
Pass by Value -- The Default Behavior
By default, C# passes all parameters by value. For reference types like strings, "by value" means the reference itself is copied, not the object. The method receives its own copy of the pointer, and both the caller's variable and the method's parameter point to the same string object in memory.
Why does original remain "Original"? Because TryChange received a copy of the reference. Reassigning text inside the method only changes where the local copy points. The caller's original variable still points to the "Original" string object.
Using ref to Pass the Reference by Reference
If you want a method to be able to change which string the caller's variable points to, you need the ref keyword. This passes the reference itself by reference, meaning the method works directly with the caller's variable rather than a copy.
Now original shows "Changed" because ChangeWithRef did not work with a copy. It directly reassigned the caller's original variable. Use ref sparingly because it makes code harder to reason about -- the caller must know the method might replace its value. The out keyword works similarly but requires the method to assign a value before returning.
Why Immutability Makes Strings Feel Like Value Types
Consider how a mutable reference type like List<T> works differently:
The list is modified because both the caller and the method share a reference to the same mutable object. With strings, this kind of shared mutation is impossible. You cannot change a string's characters in place. The only option is creating a new string, and reassignment only affects the local variable (unless you use ref). This is why strings feel like value types even though they are technically reference types.
StringBuilder for Mutable String Operations
When you need to build or modify strings efficiently inside a method, use StringBuilder. Unlike string, StringBuilder is mutable, so changes are visible to anyone holding a reference to the same instance:
StringBuilder is also more efficient when concatenating in a loop because it mutates a single buffer rather than creating a new string object on every iteration.
String Interning
.NET uses a technique called string interning to optimize memory usage. The runtime maintains a pool of unique string literals, and identical literal strings share the same object in memory:
Since strings are immutable, this sharing is always safe -- no code path can mutate the shared object.
Common Pitfalls
- Assuming that passing a string to a method lets the method change the caller's variable, when by default only the local copy of the reference is affected
- Using
reffor string parameters when returning a new string from the method would be clearer and safer - Concatenating strings in a loop instead of using
StringBuilder, which creates N intermediate string objects and degrades performance - Confusing
==(value equality) withReferenceEquals(reference identity) when comparing strings, especially with interned vs non-interned strings - Treating strings as mutable objects from other languages (like JavaScript or Python) and expecting in-place modification methods
Summary
- Strings in .NET are reference types, but their immutability makes them behave like value types when passed to methods
- By default, passing a string to a method copies the reference, so reassigning the parameter inside the method does not affect the caller
- Use the
refkeyword when you need a method to replace the caller's string variable, but prefer returning a new string instead StringBuilderprovides mutable string operations for building strings incrementally or sharing modifications through a reference- String interning lets the runtime share identical string literal objects in memory, which is safe because strings are immutable
- Always use
==for string comparison in C# rather thanReferenceEquals, since==is overloaded to compare content

