Definition
- StringBuilder.reverse() flips the character sequence in place in O(n) time.
- Stream approach uses
chars()to get an IntStream, boxes to Character, and collects into a StringBuilder with the three-argument collect. - Surrogate safety means StringBuilder.reverse() correctly handles characters outside the Basic Multilingual Plane.
- Build-then-reverse is the correct pattern; inserting at index 0 repeatedly is quadratic.
- String immutability means concatenation in a loop creates many intermediate objects.
Priya at the National Bank of India needs to reverse account reference codes for a checksum routine. She writes the code forward into a builder, then flips the whole thing in one move, rather than shuffling each letter to the front one at a time, which would take far longer.
Understanding the Concepts
What does this question ask?
The interviewer wants you to reverse a string using the Stream API and StringBuilder. This tests your understanding of String.chars(), the three-argument collect, and why build-then-reverse beats insert-at-zero.
How do you reverse a string with StringBuilder (idiomatic)?
The simplest and most efficient approach uses StringBuilder directly.
package com.bank.core;
public class StringReverseIdiomatic {
public static void main(String[] args) {
String accountRef = "SAV2001-HDFC";
String reversed = new StringBuilder(accountRef)
.reverse()
.toString();
System.out.println("Original: " + accountRef);
System.out.println("Reversed: " + reversed);
}
}
Original: SAV2001-HDFC
Reversed: CFDH-1002VAS
How do you reverse a string using chars() and a stream?
The stream approach collects characters into a StringBuilder using the three-argument collect(supplier, accumulator, combiner), then reverses.
package com.bank.core;
public class StringReverseStream {
public static void main(String[] args) {
String accountRef = "SAV2001-HDFC";
String reversed = accountRef.chars()
.mapToObj(c -> (char) c)
.collect(
StringBuilder::new,
StringBuilder::append,
StringBuilder::append)
.reverse()
.toString();
System.out.println("Reversed: " + reversed);
}
}
Reversed: CFDH-1002VAS
The three-argument collect takes a supplier (StringBuilder::new), an accumulator (append each character), and a combiner (for parallel merging). After collecting, one call to reverse() flips the entire sequence.
Why is build-then-reverse better than insert-at-zero?
| Approach | Time complexity | Reason |
|---|---|---|
| Build forward + reverse once | O(n) | Append is amortized O(1), reverse is O(n) |
| Insert at index 0 each time | O(n squared) | Each insert shifts all existing characters right |
| String concatenation in loop | O(n squared) | Each concat creates a new immutable String |
package com.bank.core;
public class ReversePerformance {
public static void main(String[] args) {
String code = "SAV2001";
// WRONG: quadratic insert at zero
StringBuilder slow = new StringBuilder();
for (char c : code.toCharArray()) {
slow.insert(0, c); // shifts all existing chars every time
}
System.out.println("Slow: " + slow);
// CORRECT: build forward, reverse once
StringBuilder fast = new StringBuilder();
for (char c : code.toCharArray()) {
fast.append(c); // amortized O(1)
}
fast.reverse();
System.out.println("Fast: " + fast);
}
}
Slow: 1002VAS
Fast: 1002VAS
Both produce the same result, but insert-at-zero is quadratic because every insert shifts the existing content.
What about Unicode surrogate pairs?
StringBuilder.reverse() is surrogate-aware. Characters outside the Basic Multilingual Plane are stored as two char values (a surrogate pair). A naive character-by-character reversal would split the pair and corrupt the text.
package com.bank.core;
public class SurrogateReversal {
public static void main(String[] args) {
// Musical symbol (U+1D11E) stored as surrogate pair
String text = "A\uD834\uDD1EB";
System.out.println("Original length: " + text.length());
// StringBuilder.reverse() keeps the pair intact
String safe = new StringBuilder(text).reverse().toString();
System.out.println("Safe reverse: " + safe);
}
}
Original length: 4
Safe reverse: B𝄞A
For plain ASCII account codes any method works, but the built-in reverse is the safe default.
What are common mistakes?
Mistake 1: using string concatenation in a loop.
// WRONG: creates a new String on every iteration
String code = "SAV2001";
String reversed = "";
for (int i = code.length() - 1; i >= 0; i--) {
reversed += code.charAt(i); // new String each time
}
// CORRECT: use StringBuilder
String correct = new StringBuilder(code).reverse().toString();
Mistake 2: forgetting that chars() returns int values, not char.
// WRONG: prints int code points
"ABC".chars().forEach(c -> System.out.print(c + " "));
// Output: 65 66 67
// CORRECT: cast to char
"ABC".chars()
.mapToObj(c -> (char) c)
.forEach(c -> System.out.print(c + " "));
// Output: A B C
What about a pure stream reduce approach?
package com.bank.core;
public class ReverseWithReduce {
public static void main(String[] args) {
String code = "SAV2001";
String reversed = code.chars()
.mapToObj(c -> String.valueOf((char) c))
.reduce("", (a, b) -> b + a);
System.out.println("Reduced: " + reversed);
}
}
Reduced: 1002VAS
This works but creates many intermediate strings because each reduce step concatenates. Use the StringBuilder approach for production code.
How is this different from using codePoints()?
| Method | Returns | Handles surrogates |
|---|---|---|
| chars() | IntStream of char values | No, splits surrogate pairs |
| codePoints() | IntStream of Unicode code points | Yes, each surrogate pair is one int |
For reversing strings with supplementary characters, prefer codePoints() with Character.toChars() to rebuild each code point correctly.
What are common interview follow-up questions?
- "How do you reverse only the words in a sentence, not the characters?" Split by space, reverse the array, and join.
- "How do you check if a string is a palindrome?" Reverse and compare, or use the index-based approach.
- "What is the time complexity of StringBuilder.reverse()?" O(n), because it swaps characters from both ends toward the middle.
Visual Overview
String: "SAV2001-HDFC"
│
│ .chars() → IntStream [83, 65, 86, 50, 48, 48, 49, 45, ...]
│
│ .mapToObj(c -> (char) c)
▼
Stream<Character> ['S', 'A', 'V', '2', '0', '0', '1', '-', ...]
│
│ .collect(StringBuilder::new, append, append)
▼
StringBuilder: "SAV2001-HDFC"
│
│ .reverse()
▼
StringBuilder: "CFDH-1002VAS"
│
│ .toString()
▼
String: "CFDH-1002VAS"
Key Takeaway
The idiomatic and most efficient way to reverse a string in Java is to wrap it in a StringBuilder and call its built-in reverse method. For a stream-based variant, use chars() to get an IntStream of character codes, box them with mapToObj, collect into a StringBuilder with the three-argument collect, and reverse once at the end.
The key efficiency point is to build forward and reverse a single time rather than inserting at index zero or prepending, which is quadratic. Never use repeated string concatenation in a loop because strings are immutable and each concatenation allocates a new object. At the National Bank of India, Rajesh used this in a checksum routine over account reference codes like SAV2001.
The senior detail worth calling out is Unicode correctness. StringBuilder.reverse() is surrogate-aware, so it keeps characters outside the Basic Multilingual Plane intact. A naive character-by-character reversal could split a surrogate pair and corrupt the text. For plain ASCII account codes any approach works, but the built-in reverse is the safe default for production.
Remember one sentence: build forward into a StringBuilder and reverse once at the end, because append is O(1) amortized and the single reverse is O(n), making the whole operation linear.
---