String Interning in C# and Memory Optimization
String interning in C# is a mechanism by which the Common Language Runtime (CLR) optimizes memory by storing only a single copy of each distinct string literal in an internal table called the intern pool. When multiple variables or components use identical string values, they are made to reference the same memory address rather than creating separate heap objects. This article explains how the intern pool works under the hood, how to manually control interning, the specific performance and memory benefits it provides, and the trade-offs developers must consider.
What Is String Interning?
In .NET, strings are reference types and are immutable. Without optimization, creating ten instances of the exact same string value would result in ten distinct objects allocated on the managed heap, each consuming additional memory.
String interning resolves this redundancy. The CLR maintains a central lookup table—a hash table containing references to unique string instances known as the intern pool. When a string is interned, the CLR checks if an identical sequence of characters already exists in this pool:
- If it exists, the CLR returns a reference to the existing string object.
- If it does not exist, the string is added to the pool, and that new reference is returned.
Automatic Interning of Literals
By default, the C# compiler and CLR automatically intern all compile-time string literals.
string a = "hello";
string b = "hello";
bool sameReference = object.ReferenceEquals(a, b); // Returns TrueIn this example, both a and b reference the
exact same memory location on the heap because the literal
"hello" is automatically interned when the assembly is
loaded.
Dynamically created strings (such as strings built at runtime via
StringBuilder, user input, or I/O operations) are
not automatically interned:
string a = "hello";
string b = new string(new char[] { 'h', 'e', 'l', 'l', 'o' });
bool sameReference = object.ReferenceEquals(a, b); // Returns FalseManual
Interning: string.Intern and
string.IsInterned
The .NET framework provides two static methods in the
System.String class to interact with the intern pool
explicitly at runtime:
string.Intern(string str): Checks if the specified string is in the pool. If found, it returns the reference to the interned string. If not found, it adds the string to the pool and returns its reference.string.IsInterned(string str): Checks whether the string is already in the intern pool without adding it. It returns the interned reference if present, ornullif it is not.
string dynamicString = new string(new char[] { 'h', 'e', 'l', 'l', 'o' });
string internedString = string.Intern(dynamicString);
bool sameReference = object.ReferenceEquals("hello", internedString); // Returns TrueHow String Interning Optimizes Memory and Performance
- Memory Deduplication: Applications that process vast amounts of repetitive data—such as parsing large JSON/XML files, CSV imports, or database records with repeated categorical values—can significantly reduce their managed heap usage by sharing single string instances instead of allocating millions of duplicate objects.
- Faster Equality Comparisons: Comparing two strings
using character-by-character evaluation (
String.Equals) has a time complexity proportional to string length (\(O(N)\)). When strings are guaranteed to be interned, they can be compared using reference equality (object.ReferenceEquals), reducing the comparison to a single, pointer-level operation (\(O(1)\)).
Trade-offs and Considerations
While string interning can yield large memory savings, it introduces specific costs:
- Lifetime and Garbage Collection: Interned strings
remain allocated for the entire lifetime of the process (or
AppDomain). The Garbage Collector (GC) does not reclaim memory used by the intern pool, meaning over-interning unique or transient strings causes permanent memory leaks. - CPU Overhead: Calling
string.Internrequires a hash-table lookup and synchronization overhead to ensure thread safety. If a string has few duplicates, the CPU cost of the lookup outweighs the memory savings. - Modern Alternatives: In modern .NET versions,
alternative approaches such as custom string pools,
ReadOnlySpan<char>, or third-party deduplication strategies are often preferred overstring.Internbecause they allow unused strings to be collected by the GC when no longer needed.