Back to Stream Practice: String & Character
Q8Question 8 of 11

Reverse a string using chars() and StringBuilder

Key Concepts
  • new StringBuilder(s).reverse().toString() is the idiomatic one-liner for reversing a string
  • Stream-based: chars() into a three-argument collect with StringBuilder, then reverse once at the end
  • Building forward and reversing once is O(n); inserting at index 0 repeatedly is O(n squared)
  • StringBuilder.reverse() is surrogate-aware and keeps supplementary characters intact
  • Never use repeated string concatenation in a loop because strings are immutable and each concat allocates a new object
  • For pure stream reversal without StringBuilder, reduce with prepend works but creates many intermediate strings

Definition

new StringBuilder(s).reverse().toString() reverses a string efficiently, and a stream-based variant collects characters into a StringBuilder before reversing.
  • 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.

JAVA
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);
    }
}
TEXT
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.

JAVA
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);
    }
}
TEXT
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 |

JAVA
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);
    }
}
TEXT
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.

JAVA
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);
    }
}
TEXT
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.

JAVA
// 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.

JAVA
// 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?

JAVA
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);
    }
}
TEXT
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

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

---

Share
Use ← → arrow keys to navigate