Java String Manipulation for Coding Interviews: The Reference Guide

June 18, 202610 min read
dsaalgorithmsinterview-prepjava
Java String Manipulation for Coding Interviews: The Reference Guide
TL;DR
  • == vs .equals(): use .equals() for content comparison — == tests reference identity and silently passes when both literals come from the string pool
  • StringBuilder over +: concatenating with + in a loop is O(n²); StringBuilder.append() is O(1) amortized with a doubling buffer
  • substring() cost: Java 7+ always copies the slice — calling it inside a loop silently turns an O(n) solution into O(n²)
  • split(".") returns []: the argument is a regex; . matches any character — use split("\\.") for a literal dot
  • Char arithmetic: c - 'a' maps letters to 0-based indices; char + int returns int, so cast back with (char) before appending to a StringBuilder
  • Method time complexity: charAt() and length() are O(1); indexOf(), contains(), replace(), and trim() are all O(n)
  • Java 11 additions: prefer strip() over trim() for Unicode correctness; isBlank() and repeat(n) are fair game on modern JDK interviews

A tweet reading "text will be called strings, and there are 12 types of numbers" with historical figures looking baffled

Java strings will betray you in a coding interview if you don't know how they actually work. They look simple. Then you compare two strings with ==, or build one in a loop, and your solution is either wrong or O(n²). Quietly. No warning. Just a no-hire. This guide covers what you need, with the exact time complexities and the pitfalls that end interviews early.

Strings Are Immutable, and That Changes Everything

Every Java String is backed by an immutable character array. That sounds like a textbook footnote, but it has a direct consequence: every method that modifies a string returns a new String object. toUpperCase(), replace(), trim(), substring(), all of them. The original is untouched.

This matters because:

String s = "hello"; s.toUpperCase(); // does nothing to s s = s.toUpperCase(); // this is what you mean

The second consequence is the string pool. String literals are interned automatically. "hello" == "hello" is true because they point to the same pool object. new String("hello") == "hello" is false because new String() always allocates a fresh object on the heap. This leads directly to the interview's most reliable Java trap.

== vs .equals(): Java's Most Loyal Footgun

String a = "hello"; String b = "hello"; String c = new String("hello"); System.out.println(a == b); // true (both from pool) System.out.println(a == c); // false (c is heap object) System.out.println(a.equals(c)); // true (content comparison)

Always use .equals() to compare string content. The only time == is correct is when you explicitly want reference equality, which is almost never in interview problems. If you need case-insensitive comparison, use .equalsIgnoreCase().

This trap works because == looks like it should compare values. In Python it does. In C it does. In Java, for strings, it checks if two variables point to the same object in memory. You'll get true enough of the time (thanks to the pool) that it seems fine, then suddenly it's not, and you're staring at a failing test case five minutes before time runs out.

IQ bell curve meme: both the lowest and highest IQ figures do "if (r === true)" while the normal crowd just does "if (r)"

Galaxy-brain Java developer: uses == on strings, gets true from the pool for 20 minutes, declares the code correct.

String vs StringBuilder: The O(n²) Cliff

Concatenating inside a loop with + is the single most expensive mistake in Java string problems.

// O(n²), each + creates a new String, copying all previous chars String result = ""; for (String word : words) { result += word; } // O(n) amortized, StringBuilder backs into a doubling array StringBuilder sb = new StringBuilder(); for (String word : words) { sb.append(word); } String result = sb.toString();

Here's what's actually happening in the first version: iteration 1 copies 1 character, iteration 2 copies 2 characters, iteration 3 copies 3 characters... you're summing 1 + 2 + 3 + ... + n. That's n(n+1)/2. That's O(n²). Every + operator in that loop allocates a brand-new String and copies everything over from scratch.

When StringBuilder runs out of capacity (default 16 characters), it doubles: newCapacity = (capacity * 2) + 2. So append() is O(1) amortized, O(n) in the rare doubling step. The final toString() is O(n).

See why string concatenation in a loop is O(n²) for the derivation.

Two wolves meme: "Inside you there are two wolves. 1 who wants to build software. 1 who wants to never use software again and build a cabin in the woods."

The exact moment you realize your "O(n)" string builder is quadratic because you used + in the loop.

The StringBuilder methods you'll actually use:

MethodTimeNotes
append(x)O(1) amortizedaccepts char, int, String, boolean
insert(i, x)O(n)shifts elements right
delete(i, j)O(n)shifts elements left
reverse()O(n)in-place on the array
charAt(i)O(1)direct array access
length()O(1)field read
toString()O(n)copies into new String

For building a result character by character, append to a StringBuilder and call toString() at the end. For reversing a string, new StringBuilder(s).reverse().toString() is the idiomatic one-liner.

Which String Methods Are O(n)? Know Before You Claim Complexity.

These come up constantly in complexity analysis. Know them cold.

MethodTimeNotes
charAt(i)O(1)direct array index
length()O(1)stored field
substring(i, j)O(j-i)always copies in Java 7+
indexOf(char)O(n)linear scan
indexOf(String)O(n·m)naive search, m = pattern length
contains(s)O(n·m)delegates to indexOf
split(regex)O(n) for simple patternsregex engine; dot needs escaping
replace(char, char)O(n)creates new string
compareTo()O(min(n,m))lexicographic
startsWith() / endsWith()O(m)m = prefix/suffix length
trim() / strip()O(n)creates new string
toCharArray()O(n)always copies
String.valueOf(int)O(d)d = digit count

The substring() trap: Before Java 7 update 6, substring() shared the backing array (O(1) time, but caused memory leaks). Since then it always copies. In interviews, treat substring() as O(k) where k is the length of the extracted portion.

The split(".") trap: The argument to split() is a regex. . matches any character. split(".") returns an empty array. Every single character is treated as a delimiter, so there's nothing left to split into. No error. No warning. Just an empty array and a confused candidate.

"a.b.c".split(".") // returns [], every char is a delimiter "a.b.c".split("\\.") // returns ["a", "b", "c"], correct

Use split("\\.") for a literal dot. split(",") for a comma is fine since , has no special regex meaning.

Working With Characters: The Part Most Candidates Get Wrong

When you need to process individual characters, toCharArray() gives you a char[] that you can index and mutate:

char[] chars = s.toCharArray(); // swap, sort, count, all fine on the array String result = new String(chars);

The Character class has the methods you need for classification:

Character.isDigit(c) // '0'-'9' Character.isLetter(c) // any Unicode letter Character.isLetterOrDigit(c) Character.isUpperCase(c) Character.isLowerCase(c) Character.toUpperCase(c) // returns char Character.toLowerCase(c) // returns char

Char arithmetic is the idiom for mapping characters to array indices:

int[] freq = new int[26]; for (char c : s.toCharArray()) { freq[c - 'a']++; // maps 'a'→0, 'b'→1, ..., 'z'→25 }

This works because Java char arithmetic operates on Unicode code points. 'a' is 97. c - 'a' gives you the 0-based index. Just make sure you're only doing this when the input is guaranteed lowercase ASCII. For mixed case, normalize first with Character.toLowerCase(c).

One subtle thing: char + int returns an int, not a char. If you're building a string character by character:

// Wrong: appends the int value 98, not 'b' sb.append('a' + 1); // Correct: cast back to char sb.append((char) ('a' + 1));

Three Conversions Worth Memorizing

These come up in almost every string-heavy problem.

int to String:

String s = String.valueOf(42); // "42" String s = Integer.toString(42); // "42", same result String s = "" + 42; // works but avoid in loops

String to int:

int n = Integer.parseInt("42"); // 42 int n = Integer.parseInt("-7"); // -7 // throws NumberFormatException on invalid input

char to its numeric value:

char c = '7'; int digit = c - '0'; // 7, maps '0'→0, '1'→1, ..., '9'→9 int digit = Character.getNumericValue(c); // also 7

The Five Pitfalls That End Interviews

1. Comparing strings with ==. It works right up until it doesn't. The string pool saves you for literals but not for anything computed at runtime. Use .equals() every time.

2. Forgetting substring() is O(k). A two-pointer approach that takes O(n) substrings can silently become O(n²). Work on index ranges instead of materializing substrings when you're slicing frequently.

3. Mutating while iterating. Strings are immutable so this can't happen directly, but if you convert to char[] and iterate while modifying, track your indices carefully.

4. split(".") eating your input. The argument is a regex. . is "any character." Your string disappears. Test your split delimiter mentally before writing it.

5. Calling toString() inside the loop. When accumulating into a StringBuilder, call toString() once at the end. Calling it inside the loop materializes a new string every iteration and defeats the purpose of using StringBuilder in the first place.

What Java 11 Added That Actually Matters

If your interviewer is on a modern JDK, these occasionally come up:

  • isBlank(): O(n), true if empty or all whitespace (Unicode-aware, unlike isEmpty())
  • strip(): O(n), like trim() but handles Unicode whitespace. Prefer strip() over trim() for correctness.
  • repeat(n): O(n), "ab".repeat(3) returns "ababab"

trim() only removes ASCII space and control characters (≤ ' '). strip() removes all Unicode whitespace. For interview problems with ASCII input, they're equivalent, but strip() is the right default.

Four Java String Patterns That Cover Most Interview Problems

Check if two strings are anagrams:

boolean isAnagram(String s, String t) { if (s.length() != t.length()) return false; int[] freq = new int[26]; for (char c : s.toCharArray()) freq[c - 'a']++; for (char c : t.toCharArray()) freq[c - 'a']--; for (int f : freq) if (f != 0) return false; return true; }

Check if a string is a palindrome (two pointers):

boolean isPalindrome(String s) { int left = 0, right = s.length() - 1; while (left < right) { if (s.charAt(left) != s.charAt(right)) return false; left++; right--; } return true; }

Reverse a string:

String reversed = new StringBuilder(s).reverse().toString();

Build a result from characters:

StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { sb.append(someChar); } String result = sb.toString();

What Actually Separates Hires on String Problems

String problems in Java look easy until the interviewer asks about complexity. Knowing that substring() is O(k), that split(".") breaks silently, and that + in a loop is O(n²) separates candidates who understand the language from candidates who just learned the syntax. The difference between a hire and a no-hire on string problems is usually one of these five pitfalls, not the algorithm.

SpaceComplexity runs voice-based mock interviews where you explain your approach out loud, including the complexity reasoning that actually gets you the offer. If you want to stress-test your Java string knowledge before the real thing, that's the place to do it.

See also:

Further Reading