String Operations
Operations vs Traversal
String traversal covers how to move through a string. String operations cover what you can do with a string using built-in methods — searching for substrings, splitting on delimiters, replacing content, trimming whitespace, and converting between cases.
Knowing the correct method call is only half the skill. Knowing the time complexity of each operation is what prevents hidden O(n²) bugs — and what interviewers test when they ask you to analyze your solution.
Every operation covered here is O(n) or O(n × m) at its worst. None is O(1). Understanding why clarifies when to precompute, cache, or avoid repeated calls.
Operation 1: Search — Finding Characters and Substrings
Finding a Character or Substring
Returns the index of the first occurrence, or -1 if not found.
1public class SearchOperations {
2
3 public static void main(String[] args) {
4 String s = "the quick brown fox jumps over the lazy dog";
5
6 // indexOf — first occurrence, O(n × m) where m = pattern length
7 System.out.println("indexOf 'fox': " + s.indexOf("fox")); // 16
8 System.out.println("indexOf 'the': " + s.indexOf("the")); // 0
9 System.out.println("indexOf 'cat': " + s.indexOf("cat")); // -1
10
11 // indexOf with fromIndex — search starting at a given position
12 System.out.println("indexOf 'the' from 1:" + s.indexOf("the", 1)); // 31
13
14 // lastIndexOf — last occurrence, O(n × m)
15 System.out.println("lastIndexOf 'the': " + s.lastIndexOf("the")); // 31
16
17 // contains — returns boolean, O(n × m) internally
18 System.out.println("contains 'fox': " + s.contains("fox")); // true
19 System.out.println("contains 'cat': " + s.contains("cat")); // false
20
21 // startsWith and endsWith — O(m) where m = prefix/suffix length
22 System.out.println("startsWith 'the': " + s.startsWith("the")); // true
23 System.out.println("endsWith 'dog': " + s.endsWith("dog")); // true
24 }
25}Output:
indexOf 'fox': 16
indexOf 'the': 0
indexOf 'cat': -1
indexOf 'the' from 1: 31
lastIndexOf 'the': 31
contains 'fox': true
startsWith 'the': true
endsWith 'dog': true
Search Complexity
Operation Time Complexity Notes indexOf(char) O(n) Scan for single character indexOf(pattern) O(n × m) Naive substring search, m = pattern length lastIndexOf(pattern) O(n × m) Scan from right contains(pattern) O(n × m) Calls indexOf internally startsWith(prefix) O(m) Only checks first m characters endsWith(suffix) O(m) Only checks last m characters Hidden trap: calling indexOf(pattern) inside a loop for each of n words, check if pattern is present → O(n × n × m) = O(n²m) Use a hash set if you need repeated membership checks
Operation 2: Split — Breaking a String into Parts
Split divides a string into an array of substrings at each occurrence of a delimiter.
1import java.util.Arrays;
2
3public class SplitOperation {
4
5 public static void main(String[] args) {
6 String csv = "Alice,Bob,Charlie,David";
7 String spaced = " hello world foo ";
8 String path = "usr/local/bin/java";
9
10 // split(delimiter) — O(n) where n = string length
11 String[] names = csv.split(",");
12 System.out.println("CSV split: " + Arrays.toString(names));
13 System.out.println("Count: " + names.length);
14
15 // split("\\s+") — one or more whitespace characters
16 String[] words = spaced.trim().split("\\s+");
17 System.out.println("Space split: " + Arrays.toString(words));
18
19 // split with limit — stop after limit parts
20 String[] parts = csv.split(",", 2);
21 System.out.println("Limit 2: " + Arrays.toString(parts)); // ["Alice", "Bob,Charlie,David"]
22
23 // split on forward slash
24 String[] segments = path.split("/");
25 System.out.println("Path split: " + Arrays.toString(segments));
26
27 // Edge case: empty string
28 String empty = "";
29 String[] emptyParts = empty.split(",");
30 System.out.println("Empty split: " + Arrays.toString(emptyParts) + " length=" + emptyParts.length);
31 }
32}Output:
CSV split: [Alice, Bob, Charlie, David]
Count: 4
Space split: [hello, world, foo]
Limit 2: [Alice, Bob,Charlie,David]
Path split: [usr, local, bin, java]
Critical Split Differences Across Languages
Behavior Java Python JavaScript
No-arg split split("\\s+") split() split(/\s+/)
(must provide) (auto strips) (with trim())
Trailing empty strings split(",") on split(",") on split(",") on
on "a,b," ["a","b",""] ["a","b",""] ["a","b",""]
← Java drops! ← Python keeps ← JS keeps
Limit parameter stops AFTER stops AFTER DIFFERENT —
on split(",", 2) limit-1 delims limit-1 delims limits RESULT count,
remaining joined remaining joined not delimiter count
Empty string split [""] length=1 [""] length=1 [""] length=1
"" → split(",")
Operation 3: Join — Building Strings from Parts
Join is the inverse of split — it combines an array of strings into one, with a separator between each element.
1import java.util.Arrays;
2import java.util.List;
3
4public class JoinOperation {
5
6 public static void main(String[] args) {
7 String[] words = {"The", "quick", "brown", "fox"};
8 List<String> letters = Arrays.asList("a", "b", "c", "d");
9
10 // String.join(delimiter, array/iterable) — O(total character count)
11 String sentence = String.join(" ", words);
12 System.out.println("Join with space: " + sentence);
13
14 String csv = String.join(",", letters);
15 System.out.println("Join with comma: " + csv);
16
17 String noSep = String.join("", words);
18 System.out.println("Join with empty: " + noSep);
19
20 // Join with a multi-character separator
21 String pathSep = String.join(" | ", words);
22 System.out.println("Join with pipe: " + pathSep);
23 }
24}Output:
Join with space: The quick brown fox
Join with comma: a,b,c,d
Join with empty: Thequickbrownfox
Join with pipe: The | quick | brown | fox
Operation 4: Replace — Substituting Content
Replace returns a new string with occurrences of a pattern substituted with a replacement. The original string is never modified.
1public class ReplaceOperation {
2
3 public static void main(String[] args) {
4 String s = "the cat sat on the mat";
5
6 // replace(char, char) — replace all occurrences of a character
7 String noT = s.replace('t', 'X');
8 System.out.println("replace 't' → 'X': " + noT);
9
10 // replace(String, String) — replace all occurrences of a substring
11 String noCat = s.replace("cat", "dog");
12 System.out.println("replace 'cat': " + noCat);
13
14 // replaceFirst — only the first occurrence
15 String first = s.replaceFirst("the", "a");
16 System.out.println("replaceFirst 'the':" + first);
17
18 // replaceAll with regex — replace all matches of a pattern
19 String digits = "a1b2c3d4".replaceAll("[0-9]", "#");
20 System.out.println("remove digits: " + digits);
21
22 String noSpaces = "hello world".replaceAll("\\s+", " ");
23 System.out.println("collapse spaces: " + noSpaces);
24
25 // Original is unchanged
26 System.out.println("Original: " + s);
27 }
28}Output:
replace 't' → 'X': Xhe caX saX on Xhe maX
replace 'cat': the dog sat on the mat
replaceFirst 'the':a cat sat on the mat
remove digits: a#b#c#d#
Original: the cat sat on the mat
Replace Complexity and the JavaScript replace Trap
replace(char, char) → O(n) — scan and replace each char
replace(str, str) → O(n × m) per replacement, r replacements → O(n × m × r)
replaceAll with regex → depends on regex complexity
JavaScript trap:
"aaa".replace("a", "b") → "baa" ← only FIRST occurrence!
"aaa".replaceAll("a", "b") → "bbb" ← all occurrences (ES2021)
"aaa".replace(/a/g, "b") → "bbb" ← regex with g flag (older approach)
Java: replace(String, String) replaces ALL occurrences — no trap
Python: replace(old, new) replaces ALL occurrences — no trap
Operation 5: Trim — Removing Whitespace
Trim removes leading and trailing whitespace. It does not touch whitespace in the middle of a string.
1public class TrimOperation {
2
3 public static void main(String[] args) {
4 String s = " Hello, World! ";
5
6 // trim() — removes leading and trailing whitespace, O(n)
7 System.out.println("Original: '" + s + "'");
8 System.out.println("trim(): '" + s.trim() + "'");
9 System.out.println("Length before: " + s.length());
10 System.out.println("Length after: " + s.trim().length());
11
12 // Java 11+: strip() handles Unicode whitespace; trim() only handles ASCII
13 System.out.println("strip(): '" + s.strip() + "'");
14 System.out.println("stripLeading(): '" + s.stripLeading() + "'");
15 System.out.println("stripTrailing(): '" + s.stripTrailing() + "'");
16
17 // trim does NOT remove internal whitespace
18 String internal = "hello world";
19 System.out.println("Internal whitespace: '" + internal.trim() + "'");
20 // → 'hello world' — middle spaces unchanged
21 }
22}Output:
Original: ' Hello, World! '
trim(): 'Hello, World!'
Length before: 20
Length after: 13
Internal whitespace: 'hello world'
Operation 6: Case Conversion
Convert all characters to uppercase or lowercase. O(n) — every character is visited.
1public class CaseConversion {
2
3 public static void main(String[] args) {
4 String mixed = "Hello, World! 123";
5
6 System.out.println("Original: " + mixed);
7 System.out.println("toUpper: " + mixed.toUpperCase());
8 System.out.println("toLower: " + mixed.toLowerCase());
9
10 // Case-insensitive comparison — always lowercase both, then compare
11 String a = "Hello";
12 String b = "HELLO";
13 System.out.println("Equal (case-insensitive): "
14 + a.toLowerCase().equals(b.toLowerCase())); // true
15 // Equivalent: a.equalsIgnoreCase(b)
16
17 // Check if a character is uppercase/lowercase
18 for (char c : mixed.toCharArray()) {
19 if (Character.isLetter(c)) {
20 System.out.print(Character.isUpperCase(c) ? "U" : "l");
21 } else {
22 System.out.print("-");
23 }
24 }
25 System.out.println();
26 }
27}Output:
Original: Hello, World! 123
toUpper: HELLO, WORLD! 123
toLower: hello, world! 123
Equal (case-insensitive): true
Operation 7: Checking String Properties
Quick boolean checks that answer common interview questions without building new strings.
1public class StringChecks {
2
3 public static void main(String[] args) {
4 String empty = "";
5 String blank = " ";
6 String alpha = "hello";
7 String alphaNum = "hello123";
8 String numeric = "12345";
9 String mixed = "Hello World";
10
11 // Empty and blank checks — O(1) and O(n) respectively
12 System.out.println("isEmpty (empty): " + empty.isEmpty()); // true
13 System.out.println("isEmpty (blank): " + blank.isEmpty()); // false
14 System.out.println("isBlank (blank): " + blank.isBlank()); // true (Java 11+)
15
16 // Character-level checks using Character class — O(n) each
17 System.out.println("All letters (alpha): "
18 + alpha.chars().allMatch(Character::isLetter)); // true
19 System.out.println("All alNum (alphaNum): "
20 + alphaNum.chars().allMatch(Character::isLetterOrDigit)); // true
21 System.out.println("All digits (numeric): "
22 + numeric.chars().allMatch(Character::isDigit)); // true
23
24 // Simple manual check (clearer in interviews)
25 boolean allDigits = true;
26 for (char c : numeric.toCharArray()) {
27 if (!Character.isDigit(c)) { allDigits = false; break; }
28 }
29 System.out.println("Manual all digits: " + allDigits); // true
30 }
31}Output:
isEmpty (empty): true
isEmpty (blank): false
isBlank (blank): true
All letters: true
All digits: true
Operation 8: Converting Between String and Number
A frequent requirement in interview problems — converting numeric strings to integers for arithmetic, or integers to strings for output.
1public class StringNumberConversion {
2
3 public static void main(String[] args) {
4 // String to integer — O(n)
5 String numStr = "12345";
6 int num = Integer.parseInt(numStr);
7 System.out.println("parseInt: " + num + " + 1 = " + (num + 1));
8
9 // Integer to string — O(log n) for the number of digits
10 int value = 42;
11 String str = String.valueOf(value); // recommended
12 String str2 = Integer.toString(value); // equivalent
13 String str3 = "" + value; // works but creates extra object
14 System.out.println("valueOf: " + str + str2);
15
16 // Handle invalid input
17 try {
18 int bad = Integer.parseInt("not_a_number");
19 } catch (NumberFormatException e) {
20 System.out.println("Invalid input: NumberFormatException caught");
21 }
22
23 // String to double
24 double d = Double.parseDouble("3.14");
25 System.out.println("parseDouble: " + d);
26 }
27}Output:
parseInt: 12345 + 1 = 12346
valueOf: 4242
Invalid input: NumberFormatException caught
parseDouble: 3.14
Operation Complexity Summary
| Operation | Time | Notes |
|---|---|---|
indexOf(char) | O(n) | Single char scan |
indexOf(pattern) | O(n × m) | Naive search, m = pattern length |
contains(pattern) | O(n × m) | Calls indexOf internally |
startsWith(prefix) | O(m) | Only checks first m chars |
endsWith(suffix) | O(m) | Only checks last m chars |
split(delimiter) | O(n) | One pass through the string |
join(parts) | O(total chars) | Copies all characters once |
replace(old, new) | O(n × m × r) | r = number of replacements |
trim() / strip() | O(n) | Scan from both ends |
toUpperCase() / toLower() | O(n) | Visit every character |
substring(l, r) | O(r - l) | Copies r-l characters |
parseInt() | O(n) | Processes each digit |
equals() | O(n) worst case | Compares char by char |
length() | O(1) | Stored as a field |
charAt(i) | O(1) | Direct address computation |
Common Mistakes Beginners Make
Using replace in JavaScript for all occurrences. "aaa".replace("a", "b") returns "baa" — only the first match is replaced. Use replaceAll("a", "b") or replace(/a/g, "b") for global replacement. Java and Python replace all occurrences by default.
Calling indexOf in a loop without understanding the cost. indexOf(pattern) is O(n × m). Calling it inside a loop of n iterations makes the total O(n² × m). If you need to check many patterns against many strings, use a hash set or precompute.
Splitting on " " (single space) instead of "\\s+". "hello world".split(" ") in Java produces ["hello", "", "world"] — an empty string between the two spaces. Split on the regex "\\s+" to handle any whitespace including multiple consecutive spaces.
Not trimming before splitting. " hello world ".split("\\s+") in Java produces ["", "hello", "world"] — an empty string at the beginning from the leading space. Always trim() first.
Treating trim() as removing all whitespace. trim() only removes leading and trailing whitespace. "hello world".trim() returns "hello world" unchanged. To collapse internal whitespace, use replaceAll("\\s+", " ") after trimming.
Converting number to string with "" + num in Java. "" + num creates an intermediate empty String object, then concatenates — marginally less efficient than String.valueOf(num). In interviews, String.valueOf() signals awareness of this.
Interview Questions
Q: What is the time complexity of String.contains() in Java?
O(n × m) where n is the string length and m is the pattern length. contains() internally calls indexOf(), which uses a naive character-by-character scan of the string for each starting position. For a 1000-character string and a 10-character pattern, the worst case is 10,000 character comparisons.
Q: Why does split("\\s+") require double backslash in Java?
Java string literals interpret \ as an escape character. To pass a literal backslash to the regex engine, you write \\ in the Java string (which becomes a single \ at runtime). The regex engine then sees \s+ and interprets \s as the whitespace character class. Python uses r'\s+' (raw string) to avoid the same issue.
Q: What is the difference between isEmpty() and isBlank() in Java?
isEmpty() returns true only if the string has zero characters — "". isBlank() (Java 11+) returns true if the string contains only whitespace — "", " ", "\t\n". Use isEmpty() for checking no characters, isBlank() for checking no meaningful content.
Q: How do you reverse a string without using a built-in reverse method?
Build the reversed string by iterating backward and appending each character to a StringBuilder (Java), a list then joining (Python), building a result string left-to-right by appending from the right end (all languages), or using two-pointer swap on a char[] (Java) or list (Python). The key insight is that strings are immutable in Java, Python, and JavaScript — you cannot reverse in-place, so you must create a new structure.
FAQs
Does split() in Python handle trailing empty strings like Java?
Python's split() with a delimiter argument keeps trailing empty strings: "a,b,".split(",") returns ['a', 'b', '']. Java's split() silently drops trailing empty strings by default — same input returns ['a', 'b']. To keep them in Java: split(",", -1). This difference trips up developers who switch between languages.
Is String.valueOf(int) faster than Integer.toString(int) in Java?
They produce identical output and have the same performance — String.valueOf(int) calls Integer.toString(int) internally. The choice is style. String.valueOf() is slightly more general (handles null for object arguments) while Integer.toString() is more explicit about what it is converting.
In Python, does str.replace() modify the original string?
No. Python strings are immutable. s.replace(old, new) returns a new string with replacements applied. The original s is unchanged. You must reassign: s = s.replace(old, new) to "update" the variable. This is different from C++ where std::string::replace() modifies the string in place.
What is the fastest way to check if a string contains only digits?
In Python: s.isdigit() — O(n), clean, handles edge cases. In Java: s.matches("\\d+") — compiles the regex each call, can be slow for repeated checks; better to use Integer.parseInt() in a try-catch or a manual loop. In C++: all_of(s.begin(), s.end(), ::isdigit) — O(n), efficient. The manual loop with early exit is optimal in all languages when you need to stop as soon as a non-digit is found.
Quick Quiz
Question 1: "hello world".indexOf("world") returns:
- ›A) 5
- ›B) 6
- ›C) 4
- ›D) -1
Answer: B) 6. The string "hello world" has 'w' at index 6 (h=0, e=1, l=2, l=3, o=4, space=5, w=6). indexOf returns the starting index of the first character of the found substring.
Question 2: What does "a,b,,c,".split(",") return in Java?
- ›A)
["a", "b", "", "c", ""] - ›B)
["a", "b", "", "c"] - ›C)
["a", "b", "c"] - ›D)
["a", "b", " ", "c", " "]
Answer: B) ["a", "b", "", "c"]. Java's split(delimiter) keeps interior empty strings (between b and c) but silently drops trailing empty strings (the empty string after the final comma). To keep trailing empty strings, use split(",", -1).
Question 3: In JavaScript, "aaa".replace("a", "b") returns:
- ›A)
"bbb"— all occurrences replaced - ›B)
"baa"— only first occurrence replaced - ›C)
"aba"— every other occurrence replaced - ›D)
"aab"— only last occurrence replaced
Answer: B) "baa". JavaScript's replace(string, replacement) replaces only the first occurrence. To replace all, use replaceAll("a", "b") or replace(/a/g, "b").
Question 4: What is the time complexity of String.join(" ", arrayOfNWords) where each word has average length k?
- ›A) O(n)
- ›B) O(k)
- ›C) O(n × k)
- ›D) O(n²)
Answer: C) O(n × k). Join copies every character from every word into a new string, plus n-1 separator characters. Total characters = n × k + (n-1) × 1 = O(n × k). This is the minimum possible cost for join — you cannot produce the result without writing each output character exactly once.
Summary
String operations fall into two categories: those that take O(1) by reading stored metadata (length(), charAt(i)) and those that must scan characters and therefore cost O(n) or more.
The key operations and their complexities:
- ›
charAt(i)andlength()→ O(1) — direct property reads - ›
indexOf(pattern),contains()→ O(n × m) — naive scan - ›
startsWith(prefix),endsWith(suffix)→ O(m) — check first or last m chars only - ›
split(delimiter)→ O(n) — single pass - ›
join(parts)→ O(total characters) — copy every character once - ›
replace(old, new)→ O(n × m × r) — depends on pattern and replacement count - ›
trim(),toUpperCase(),toLowerCase()→ O(n) — visit every character - ›
substring(l, r)→ O(r - l) — copy r-l characters
Watch for these recurring traps: replace in JavaScript replaces only the first occurrence; split(" ") leaves empty strings for multiple spaces; trim() does not touch internal whitespace; indexOf called inside a loop gives O(n²).
In the next topic, you will explore Mutable vs Immutable Strings — understanding StringBuilder, StringBuffer, why they exist, and exactly when to use each.