C# String Manipulation for Coding Interviews: The Reference Guide

June 19, 20269 min read
dsaalgorithmsinterview-prepdata-structures
C# String Manipulation for Coding Interviews: The Reference Guide
TL;DR
  • Strings are immutable in C#: every concatenation allocates a new object; building one in a loop is O(n²) without StringBuilder
  • StringBuilder for loops: O(1) amortized append, single O(n) ToString() at the end
  • char[] round-trip: s.ToCharArray() then new string(arr) is the standard C# pattern for in-place-style mutation
  • == compares values, not references: unlike Java, C# overloads the operator on strings; use StringComparison.Ordinal for explicit, fast comparison
  • char.IsLetter, char.ToLower, c - 'a': the three char tools behind most frequency-count and palindrome problems
  • Fixed int[26] beats Dictionary: for lowercase ASCII, count[c - 'a'] gives O(1) with lower constants than a hash map
  • Substring(start, length) not (start, end): C#'s off-by-one trap when coming from Python or JavaScript

Nested loops descending into an abyss of closing braces

Somewhere right now, a developer is concatenating strings in a loop, creating a brand new string object on every iteration, and wondering why their anagram check is so slow. Don't be that person. C# strings look mutable. They feel mutable. They are not.

Immutability is the one thing to internalize before your interview. Everything else follows from it.


Strings Are Immutable. Every Modification Costs You.

In C#, strings are immutable reference types. When you "modify" a string, you're actually allocating a new one and throwing the old one to the garbage collector.

string s = "hello"; s = s + " world"; // allocates a brand new string; old one is gone

Fine for one concatenation. In a loop, that's O(n²) total work for n iterations. The classic mistake:

// DO NOT DO THIS string result = ""; foreach (char c in chars) result += c; // O(n²), each iteration allocates a new string

Every pass through that loop: allocate, copy everything you had, add one character, discard the old allocation. For a 10,000-character string, you've done roughly 50 million copy operations. Your interviewer is watching the time tick.

If you're building a string character by character, or across multiple steps, use StringBuilder instead.

Homer Simpson holds up code while Marge stares in disbelief Us, discovering we've been allocating a fresh string object 10,000 times in a single loop.


The Methods That Matter (and What They Cost)

MethodTimeNotes
s.LengthO(1)Property, not a method call
s[i]O(1)Direct index access
s.Substring(start, len)O(len)Allocates new string
s.IndexOf(target)O(n·m)n = string length, m = target length
s.Contains(target)O(n·m)Wraps IndexOf
s.StartsWith(prefix)O(m)m = prefix length
s.EndsWith(suffix)O(m)m = suffix length
s.Replace(old, new)O(n)Full scan + allocation
s.Split(delim)O(n)Returns array
s.Trim()O(n)Strips leading/trailing whitespace
s.ToLower() / s.ToUpper()O(n)Locale-aware; allocates new string
string.Concat(a, b)O(n+m)Single allocation
string.Join(sep, parts)O(n)Single allocation
s.ToCharArray()O(n)Returns char[]
new string(char[])O(n)Build string from char array
s.CompareTo(other)O(min(n,m))Lexicographic

s.ToCharArray() followed by new string(char[]) is the standard C# pattern for in-place-style manipulation. Convert to an array, mutate the array, convert back. Strings can't be mutated; char[] can.

string Reverse(string s) { char[] chars = s.ToCharArray(); int left = 0, right = chars.Length - 1; while (left < right) { (chars[left], chars[right]) = (chars[right], chars[left]); left++; right--; } return new string(chars); }

char Methods Worth Memorizing

C# gives you static helpers on char for the one-liners you'll use in every other problem:

char.IsLetter(c) // a-z, A-Z (and Unicode letters) char.IsDigit(c) // 0-9 char.IsLetterOrDigit(c) char.IsWhiteSpace(c) // space, \t, \n, etc. char.IsUpper(c) char.IsLower(c) char.ToUpper(c) // returns char char.ToLower(c) // returns char (int)c // cast to ASCII/Unicode code point (char)(c - 'a' + 'A') // manual case shift (safe for ASCII)

All O(1). In tight interview loops, char.IsLetter and char.ToLower are your workhorses. For frequency counting, c - 'a' gives you a zero-indexed bucket for lowercase ASCII letters, which is how you build a 26-element frequency array instead of a hash map.


String Equality: C# Is Not Java

This one gets people coming from Java. In Java, == on strings compares references. Two strings with the same characters can fail a == check. It's a well-documented gotcha and has been causing bugs since approximately forever.

In C#, == on strings compares values, not references. The operator is overloaded.

string a = "hello"; string b = "hel" + "lo"; Console.WriteLine(a == b); // true, value comparison Console.WriteLine(a.Equals(b)); // also true

You'll still see .Equals used when a string might be null. Calling .Equals on a null reference throws; == with null is safe. For null safety, use the static form:

string.Equals(a, b, StringComparison.Ordinal)

For case-insensitive comparison, common in word-based interview problems:

string.Equals(a, b, StringComparison.OrdinalIgnoreCase)

Ordinal comparisons are byte-by-byte and faster than culture-aware ones. Unless the problem specifically calls for locale-sensitive behavior, always use Ordinal or OrdinalIgnoreCase.


StringBuilder: When and How

StringBuilder is a mutable buffer backed by a char[]. Append is O(1) amortized. ToString() at the end is O(n), one allocation. That's it.

var sb = new StringBuilder(); foreach (char c in source) { if (char.IsLetter(c)) sb.Append(char.ToLower(c)); } string result = sb.ToString(); // O(n), one allocation, done

The rule of thumb: if you have a loop that builds a string, use StringBuilder. Three or fewer concatenations of known strings, + or string.Concat is fine.

Key StringBuilder methods:

sb.Append(value) // add to end sb.Insert(index, value) // insert at position sb.Remove(index, count) // delete chars sb.Replace(old, new) // in-place replace sb.Length // current length sb[i] // index into buffer sb.ToString() // finalize to string

sb.Length = 0 resets the buffer without reallocating. Handy when you're reusing a StringBuilder across loop iterations.


C# String Manipulation Patterns for Interviews

Palindrome Check

bool IsPalindrome(string s) { int left = 0, right = s.Length - 1; while (left < right) { if (s[left] != s[right]) return false; left++; right--; } return true; }

O(n) time, O(1) space. No allocations. This is the answer every interviewer wants to see.

Anagram Check

bool IsAnagram(string s, string t) { if (s.Length != t.Length) return false; int[] count = new int[26]; foreach (char c in s) count[c - 'a']++; foreach (char c in t) count[c - 'a']--; foreach (int n in count) if (n != 0) return false; return true; }

O(n) time, O(1) space. The fixed 26-element array is worth calling out explicitly. A Dictionary<char, int> works too, but the constant factor is higher and "O(1) space via fixed array" is a talking point interviewers notice.

First Non-Repeating Character

char FirstNonRepeating(string s) { int[] count = new int[26]; foreach (char c in s) count[c - 'a']++; foreach (char c in s) if (count[c - 'a'] == 1) return c; return '#'; }

Two passes, O(n) time, O(1) space.

Reverse with LINQ

string reversed = new string(s.Reverse().ToArray());

This works. It allocates a char[] intermediate. Fine when brevity matters; use the two-pointer approach when the interviewer asks about space.

Sliding Window on a String

// Longest substring with at most k distinct characters int LongestSubstringKDistinct(string s, int k) { var count = new Dictionary<char, int>(); int left = 0, maxLen = 0; for (int right = 0; right < s.Length; right++) { count[s[right]] = count.GetValueOrDefault(s[right]) + 1; while (count.Count > k) { count[s[left]]--; if (count[s[left]] == 0) count.Remove(s[left]); left++; } maxLen = Math.Max(maxLen, right - left + 1); } return maxLen; }

The Pitfalls That Get C# Coders

Five things that will make a C# interviewer twitch.

Real coding interview screen showing a character frequency bug: dict[i] = 0 instead of dict[i] = 1 Counting character frequencies in an interview. It's always the off-by-one in the initialization.

1. null vs empty vs whitespace. Three distinct states. All different. All wrong if you handle them the same way.

string.IsNullOrEmpty(s) // null or "" string.IsNullOrWhiteSpace(s) // null, "", or " "

Call IsNullOrEmpty before indexing. Accessing s[0] on null throws immediately.

2. Substring takes a length, not an end index. This is different from Python slicing and will trip you up if you don't expect it.

s.Substring(2, 3) // chars at index 2, 3, 4 (length 3) // Python equivalent: s[2:5]

C# 8+ added range syntax: s[2..5] (start inclusive, end exclusive). You can use it if the environment supports it. Ask first.

3. Char arithmetic returns int. 'a' + 1 gives you 98, not 'b'. C# is being helpful. You will not feel helped.

char next = (char)('a' + 1); // 'b', explicit cast required

This catches people mid-interview. The cast is one word. Put it in there.

4. ToLower on the whole string allocates. In a loop where you're comparing characters, char.ToLower(c) (static, returns char, O(1)) is cheaper than calling s.ToLower() on the full string just to look at one character.

5. Split with multiple separators needs an array. s.Split(' ') splits on a single space. Multiple separators:

s.Split(new[] {' ', ','}, StringSplitOptions.RemoveEmptyEntries)

Forgetting RemoveEmptyEntries gives you empty strings between consecutive delimiters. Add it by default.


Quick Reference: Patterns by Problem Type

Problem TypeC# Tool
Build string in loopStringBuilder
Mutate individual charschar[] arr = s.ToCharArray() then new string(arr)
Frequency count (lowercase ASCII)int[26], index with c - 'a'
Frequency count (arbitrary)Dictionary<char, int>
Case-insensitive comparestring.Equals(a, b, StringComparison.OrdinalIgnoreCase)
Null-safe checkstring.IsNullOrEmpty(s)
Reverse stringTwo-pointer on char[] or s.Reverse().ToArray()
Check palindromeTwo-pointer, no allocation
Split into wordss.Split(' ') or s.Split(separators, RemoveEmptyEntries)

The pitfalls section covers what trips people up most. The patterns section covers what comes up most often. The rest is reps.

If you want to practice explaining your string logic out loud under interview conditions, SpaceComplexity runs voice-based mock interviews with rubric-based feedback on both your code and your communication.


Further Reading

Related posts: