DSA Tutorial
🔍

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.

Java
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.

Java
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.

Java
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.

Java
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.

Java
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.

Java
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.

Java
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.

Java
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

OperationTimeNotes
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 caseCompares 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) and length() → 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.

String Operations