Skip to content
Search lessons, topics, tests…
Esc

    ↑ ↓ moveEnter openEsc close
    Cheat sheets: all cheat sheets

    String Immutability in C#: Allocations, Sharing and Surprises

    Trace shared string references and reassignment.

    Learning outcome

    By the end of this lesson, you should be able to explain three closely related ideas: a C# string is immutable, assignment copies the reference rather than the text, and operations that look like edits return a string result without changing the original. This matters in interviews because it explains surprising behavior, performance tradeoffs, and why strings are safe to share across APIs and threads.

    Intuition

    Think of a string as a sealed container of text and a variable as a label pointing to it. Several labels can point to the same container. Reassigning one label does not alter the text or move the other labels. This analogy explains sharing and reassignment; it does not predict allocations or garbage-collection timing.

    The executable example below stores original + "world" in modified.

    Neither original nor alias is reassigned in that example. Both still refer to "Hello "; modified refers to "Hello world".

    Deep dive

    C# string is a reference type whose contents are read-only after construction. Methods such as Replace, Substring, and ToUpperInvariant, and concatenation expressions, return string results without editing the existing object. Do not equate a returned result with a guaranteed fresh allocation: a no-op can reuse the current instance. See the C# strings guide for immutability and the String.Replace contract for the no-match case.

    This has a few practical consequences:

    1. Assignment shares references, not text copies.

    For example, after var a = "cat"; var b = a;, both variables point to the same string object.

    1. Apparent edits return results.

    After a = a.Replace("c", "b");, a holds "bat", while b still holds "cat". In contrast, var unchanged = b.Replace("z", "x"); finds no match, so the documented result is the current instance: object.ReferenceEquals(b, unchanged) is True. In both cases the original text is untouched.

    1. Literals are already strings.

    A literal such as "hello" supplies a string value; it does not imply a fresh allocation every time it appears. Interning can share equal strings. Reason about text equality rather than depending on literal reference identity. See String.Intern remarks.

    1. Immutability improves safety.

    Because normal string operations cannot change an existing instance, you can share its text across methods and threads without another caller editing it underneath you. A shared variable that is reassigned still needs appropriate coordination; immutability does not make a read-modify-write operation such as shared += "x" atomic.

    1. Performance changes with repeated edits.

    Repeated concatenation in a loop can allocate many intermediate strings. For many sequential edits, consider StringBuilder: append into its mutable storage and call ToString() once for the final result. The builder and its growing storage can still allocate. For a few fixed concatenations, ordinary strings can be simpler and just as suitable; measure a real workload before claiming a speedup. See StringBuilder performance guidance.

    The key interview phrase is: strings are immutable, but variables are not. The variable can point somewhere else; the string object itself does not change.

    Failure modes

    Common mistakes to watch for:

    • Assuming Replace, Trim, ToUpperInvariant, or += changes the original string in place.
    • Expecting an alias variable to reflect later “modifications” made through another reference.
    • Using string concatenation in large loops and then being surprised by allocation overhead.
    • Confusing immutability with interned string identity. Two equal strings can compare as equal without being the same object reference.

    A useful mental model is: if the API sounds like it edits text, check its return value and contract. The original string stays unchanged; whether the result reuses an instance is a separate question.

    Interview drill

    Try answering these out loud:

    1. Why does s2 keep the old value after s1 += "x"?
    2. Why is string safe to share across threads?
    3. When would you choose StringBuilder over repeated concatenation?
    4. What is the difference between changing a variable reference and changing the object it refers to?
    5. Why can immutable objects still be highly useful in performance-critical code?

    Suggested answers

    1. Assuming s2 = s1 happened first, s1 += "x" assigns the concatenated result to s1. s2 keeps its existing reference and therefore the old text.
    2. Readers sharing one string see stable contents because its characters cannot be edited through normal string APIs. If threads also replace a shared variable, coordinate that update separately. Two concurrent += operations can read the same old value and lose one update.
    3. Choose StringBuilder for many incremental appends, especially when the number of pieces is not known beforehand. Convert once after building. For a small fixed expression, start with ordinary concatenation and measure before adding complexity.
    4. Reassignment changes which object one variable identifies. Mutation would change an object's contents for every alias. In the final program, modified receives a different value while original and alias retain theirs.
    5. Sharing stable text avoids copying it merely to protect it from another reader. That can simplify APIs and reduce defensive-copy costs. Immutability does not eliminate allocations when building new values, so choose the construction strategy to fit the workload.

    Revision checklist

    • string is immutable.
    • Assigning one string variable to another copies the reference.
    • String operations return results without changing the original; a no-match Replace returns the current instance.
    • Old strings never become mutable. An unreachable object may be reclaimed later; losing one local reference does not prove it is unreachable. See garbage-collection fundamentals.
    • Consider StringBuilder for many sequential edits.
    • Immutability helps correctness, sharing, and safe concurrent reads; it does not synchronize variable updates.

    Worked example

    The program below shows the two most important behaviors: the original string remains unchanged after a “modification,” and an alias keeps the old value. It also demonstrates the StringBuilder append-then-convert pattern. Its three iterations illustrate usage; they are not a performance benchmark.

    Code walkthrough

    • original and alias start out pointing to the same string object.
    • modified = original + "world" does not alter the old object; it creates a new one.
    • object.ReferenceEquals(original, alias) is True immediately after assignment because both variables refer to the same object.
    • object.ReferenceEquals(original, modified) is False because concatenation produced a different string instance.
    • The StringBuilder loop shows the standard pattern for many edits: append into a mutable buffer, then convert once at the end.

    A good interview answer should mention both semantics and performance: immutability gives predictable behavior, while StringBuilder avoids the cost of producing many temporary strings.

    Executable code examples

    Immutability and aliasing demo

    Run this in a separate .NET console project: dotnet new console -n StringImmutabilityDemo, replace its Program.cs with the listing below, then run dotnet run --project StringImmutabilityDemo. No extra packages are needed.

    Program.cs

    C#
    using System;
    using System.Text;
    
    var original = "Hello ";
    var alias = original;
    var modified = original + "world";
    
    Console.WriteLine(original);
    Console.WriteLine(alias);
    Console.WriteLine(modified);
    Console.WriteLine(object.ReferenceEquals(original, alias));
    Console.WriteLine(object.ReferenceEquals(original, modified));
    
    var builder = new StringBuilder();
    for (int i = 1; i <= 3; i++)
    {
        builder.Append("item ").Append(i).Append(';');
    }
    
    Console.WriteLine(builder.ToString());

    Expected output and explanation

    In the following output, the first two lines each contain one trailing space after Hello.

    Text
    Hello 
    Hello 
    Hello world
    True
    False
    item 1;item 2;item 3;
    1. The assignment alias = original makes both variables refer to the same object, so both print the original text.
    2. Concatenating the nonempty suffix produces "Hello world" for modified. The next two lines distinguish shared reference identity (True) from the different result object (False).
    3. The loop appends the three item numbers and a semicolon after each one. The final ToString() supplies the last printed line. No timing or allocation count is measured.

    TechInterviewNotes Editorial Team · Updated 25 Oct 2026 ·