C# string interview questions, with answers
Strings are the most used type in any C# program, and interview questions about them test whether you know what the type really is: an immutable reference type that compares like a value with ==. The questions follow from that: why building a string in a loop is slow, which strings share memory, how the literal forms treat backslashes and quotes, and why comparing text is harder than it looks once cultures are involved. Every answer below comes with code that was compiled and run on .NET 10.
The questions start with immutability and building strings, move through literals and formatting, and end with comparison, missing text and slicing without copying. Then take the free C# diagnostic — ten questions across every C# topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.Are C# strings immutable, and why does += in a loop get slow?
In short: Yes: every method that seems to change a string returns a new one, so += in a loop copies the whole text each time, while StringBuilder appends in place.
A string's characters never change after it is created. Replace, ToUpper, Trim and += all return a new string and leave the original untouched, which makes strings safe to share between threads, safe as dictionary keys, and lets the runtime share identical literals. The cost appears when building text piece by piece: s += piece allocates a new string and copies everything so far, so n appends cost O(n²) character copies. System.Text.StringBuilder keeps a growable buffer and appends in place, and ToString() produces the final string once; the code below builds a list and trims the last comma by shortening Length. For a few fixed pieces, + or interpolation is fine, and string.Join suits a whole collection.
using System.Text; var sb = new StringBuilder(); for (int i = 0; i < 4; i++) sb.Append(i).Append(','); sb.Length--; Console.WriteLine(sb); // 0,1,2,3
2.What is string interning in C#?
In short: The runtime keeps one shared instance of each literal string, so identical literals are the same object; strings built at run time are separate objects unless you call string.Intern.
String literals in a program are interned: the runtime keeps a pool, and every occurrence of the same literal refers to one shared instance, which saves memory. A string built at run time, by concatenation, from input or with new string(...), is a new object even when its text matches a literal. string.Intern(s) returns the pooled instance for s's text, adding it if necessary, and string.IsInterned checks without adding. Interning only matters when references are compared, with ReferenceEquals or == on variables typed as object; string's own == compares characters, so correct code gives the same answer either way. Interned strings are never collected, so interning large amounts of run-time text leaks memory.
string a = "tea"; string b = new string('t', 1) + "ea"; Console.WriteLine( ReferenceEquals(a, b)); string c = string.Intern(b); Console.WriteLine( ReferenceEquals(a, c)); // False // True
3.What is string interpolation in C#, and how does it relate to string.Format?
In short: $"...{expr}..." embeds expressions in a string, with the same alignment and format specifiers as string.Format, and the compiler turns it into efficient formatting code.
An interpolated string such as $"{name} scored {pct:P0}" evaluates each expression in braces and formats it, and after a colon it accepts the same format strings as string.Format and ToString, such as N2, P0 or a date pattern, plus an alignment, {x,3}, that pads to a width. It replaced positional placeholders such as {0}, which were easy to get out of order. Since C# 10 the compiler builds interpolated strings with a handler that avoids boxing and intermediate strings. Formatting uses the current culture, so numbers and dates can differ between machines; FormattableString.Invariant or an explicit IFormatProvider fixes the culture for text that is stored or parsed. Literal braces are written {{ and }}.
int score = 7, total = 9; double pct = (double)score / total; Console.WriteLine( $"{score,3}/{total}"); Console.WriteLine($"{pct:F2}"); // 7/9 // 0.78
4.What are verbatim and raw string literals in C#?
In short: A verbatim string, @"...", ignores backslash escapes and may span lines; a raw string literal, from C# 11, also allows quotes inside without any escaping.
In a regular literal, a backslash starts an escape sequence, so a Windows path needs every backslash doubled. Prefixing the literal with @ makes it verbatim: backslashes are ordinary characters, the literal may span several lines, and the only escape left is a doubled quote. C# 11 added raw string literals, which begin and end with three or more double quotes: nothing inside needs escaping, not even quotes, and when the literal spans lines the indentation of the closing quotes is removed from every line, which makes embedded JSON, SQL or regular expressions readable, as below. Both combine with interpolation, as $@"..." and as a raw literal preceded by $.
string path = @"C:\data\new"; string json = """ {"id": 7} """; Console.WriteLine(path); Console.WriteLine(json); // C:\data\new // {"id": 7}
5.How should you compare strings in C#, and why does culture matter?
In short: Say which comparison you mean: StringComparison.Ordinal for identifiers and keys, OrdinalIgnoreCase for case-insensitive keys, and culture-aware comparison only for text shown to people.
== and string.Equals compare ordinally, character by character, but methods such as StartsWith(string), IndexOf(string) and CompareTo use the current culture, whose rules for case and accents vary. The famous trap is Turkish, where the capital of i is İ, so ToUpper() and culture-aware case-insensitive comparisons of "file" and "FILE" can disagree on a Turkish machine. The guidance is to pass a StringComparison explicitly: Ordinal or OrdinalIgnoreCase for file names, keys, protocol text and anything machine-readable, as below, and CurrentCulture only when sorting or matching text for display. Dictionaries and sets take a comparer as well, as in new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase).
var cmp = StringComparison .OrdinalIgnoreCase; string a = "Pune"; Console.WriteLine(a == "PUNE"); Console.WriteLine( a.Equals("PUNE", cmp)); // False // True
6.What is the difference between null, an empty string and whitespace in C#, and how do you test for them?
In short: null means no string at all, an empty string has length zero, and whitespace looks empty but is not; string.IsNullOrEmpty and IsNullOrWhiteSpace test for them safely.
A null string reference has no object behind it, so calling any member on it, even Length, throws NullReferenceException, while "" and string.Empty are the same zero-length string, fully usable. Input from users often arrives as spaces, which a plain emptiness check misses. string.IsNullOrEmpty(s) returns true for null or "", and string.IsNullOrWhiteSpace(s) also returns true for strings of only spaces, tabs or newlines, as below, which is usually the right test for a required form field. Both are static methods, so they are safe to call with null, and they are annotated for nullable reference types, so after a false result the compiler knows the string is not null.
string?[] inputs = { null, "", " ", "a" }; foreach (var s in inputs) { bool b = string .IsNullOrWhiteSpace(s); Console.Write(b ? 1 : 0); } // 1110
7.What are Span<T> and ReadOnlySpan<char>, and why do they matter for string processing?
In short: A span is a lightweight view over contiguous memory; slicing a string as ReadOnlySpan<char> avoids allocating substrings, which makes parsing much cheaper.
string.Substring allocates a new string every time, so a parser that cuts a line into fields creates garbage for every field. ReadOnlySpan<char> is a ref struct holding a reference and a length: s.AsSpan() gives a view of the string's characters, Slice(start, length) narrows the view without copying, and many APIs, including int.Parse and IndexOf, accept spans directly, as below. Because a span may point into the stack or native memory, it can only live on the stack: it cannot be a field of a class, be boxed, or be held across an await. Memory<T> is the heap-safe counterpart for asynchronous code, and the whole family is what lets .NET's own parsers and formatters run without allocating.
string line = "id=35;qty=7"; var span = line.AsSpan(); int eq = span.IndexOf('='); int semi = span.IndexOf(';'); var num = span.Slice(eq + 1, semi - eq - 1); Console.WriteLine( int.Parse(num) * 2); // 70
How the diagnostic asks it
One question from the C# bank, exactly as a sitting would show it. The bank has 3 on strings and 30 across C#.
What does this C# code print?
string a = "hello"; string b = new string("hello".ToCharArray()); Console.WriteLine($"{a == b} {(object)a == (object)b}");
- 1True True
- 2False False
- 3False True
- 4True Falsecorrect
string overloads == to compare contents, so a == b is True even though b is a different object, built at run time from a char array. Which == runs is decided at compile time from the operands' static types, though: cast both to object and == becomes reference equality, and since a and b are different objects, that is False. False False assumes C# compares strings by reference. True True assumes identical text means one object; only literals and interned strings share an object. False True reverses the two rules. Code that compares strings typed as object, such as keys in a non-generic collection, therefore needs Equals, not ==.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 C# questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.