Java
StringBuilder
prepend strings
programming
tutorial

How can we prepend strings with StringBuilder?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Java's StringBuilder does not have a prepend method. The standard solution is insert(0, value), which shifts existing content to the right and places the new value at position zero. This works correctly for occasional front insertions, but repeated prepends in a loop carry an O(n) cost per call because the internal character array must be shifted each time.

java
StringBuilder sb = new StringBuilder("world");
sb.insert(0, "hello ");
System.out.println(sb); // hello world

This article covers the basic pattern, the performance characteristics, alternative data structures for prepend-heavy workloads, and the capacity management details that affect both approaches.

Basic Prepend with insert(0, ...)

insert(int offset, ...) is the overloaded method that handles front insertion. Index 0 means "before the first character."

java
1public class PrependExample {
2    public static void main(String[] args) {
3        StringBuilder sb = new StringBuilder("world");
4        sb.insert(0, "hello ");
5        System.out.println(sb); // hello world
6    }
7}

The method returns the same StringBuilder instance, so you can chain calls if you want (though chaining inserts at position 0 can be confusing to read).

java
StringBuilder sb = new StringBuilder("!");
sb.insert(0, "world").insert(0, "hello ");
System.out.println(sb); // hello world!

Supported Overloads

Like append, insert is overloaded for many types. You do not need to convert values to strings before inserting.

java
1public class OverloadExample {
2    public static void main(String[] args) {
3        StringBuilder sb = new StringBuilder(" items in stock");
4
5        sb.insert(0, 42);          // int
6        sb.insert(0, "Count: ");   // String
7        sb.insert(7, true);        // boolean after "Count: "
8
9        System.out.println(sb);    // Count: true42 items in stock
10    }
11}
OverloadParameter TypeExample
insert(0, String)Stringsb.insert(0, "prefix")
insert(0, char)charsb.insert(0, '>')
insert(0, int)intsb.insert(0, 42)
insert(0, long)longsb.insert(0, 100L)
insert(0, double)doublesb.insert(0, 3.14)
insert(0, boolean)booleansb.insert(0, true)
insert(0, char[])char[]sb.insert(0, charArray)
insert(0, CharSequence)CharSequencesb.insert(0, otherBuilder)
insert(0, Object)Object (calls toString)sb.insert(0, myObject)

Why Prepending is Slower than Appending

StringBuilder stores characters in a backing char[] (or byte[] in compact strings on JDK 9+). Appending writes to the end, which is O(1) amortized because the array grows when needed but existing characters stay put. Prepending forces every existing character to shift right by the length of the inserted content.

For a single prepend on a builder of length n, the cost is O(n) because all n characters must move. If you prepend k times in a loop, the total cost is O(k * n_avg), which can approach O(k^2) for large sequences.

java
1public class PrependBenchmark {
2    public static void main(String[] args) {
3        int iterations = 100_000;
4
5        // Append: fast
6        long start = System.nanoTime();
7        StringBuilder appendBuilder = new StringBuilder();
8        for (int i = 0; i < iterations; i++) {
9            appendBuilder.append(i).append(' ');
10        }
11        long appendTime = System.nanoTime() - start;
12
13        // Prepend: slow
14        start = System.nanoTime();
15        StringBuilder prependBuilder = new StringBuilder();
16        for (int i = 0; i < iterations; i++) {
17            prependBuilder.insert(0, ' ').insert(0, i);
18        }
19        long prependTime = System.nanoTime() - start;
20
21        System.out.printf("Append:  %d ms%n", appendTime / 1_000_000);
22        System.out.printf("Prepend: %d ms%n", prependTime / 1_000_000);
23    }
24}

On typical hardware, the prepend version runs 100x to 1000x slower than the append version at 100K iterations. The difference is negligible for small counts (under a few hundred), but it matters for algorithmic string construction.

Alternative: Collect in a Deque, Then Build

If your algorithm naturally produces content front-to-back, the cleanest workaround is to collect parts in a Deque (double-ended queue) and build the final string in one pass.

java
1import java.util.ArrayDeque;
2import java.util.Deque;
3
4public class DequeApproach {
5    public static void main(String[] args) {
6        Deque<String> parts = new ArrayDeque<>();
7        parts.addFirst("world");
8        parts.addFirst(", ");
9        parts.addFirst("hello");
10
11        StringBuilder sb = new StringBuilder();
12        for (String part : parts) {
13            sb.append(part);
14        }
15
16        System.out.println(sb); // hello, world
17    }
18}

addFirst on ArrayDeque is O(1) amortized. The final append loop is O(total_length). This avoids the quadratic shifting cost entirely.

Alternative: Append in Reverse, Then Reverse

When you are building a string character by character in reverse order (common in number-to-string conversions or stack-based algorithms), append each character and reverse the result.

java
1public class ReverseApproach {
2    public static void main(String[] args) {
3        // Convert integer to string manually
4        int number = 12345;
5        StringBuilder sb = new StringBuilder();
6
7        while (number > 0) {
8            sb.append((char) ('0' + number % 10));
9            number /= 10;
10        }
11
12        sb.reverse();
13        System.out.println(sb); // 12345
14    }
15}

This pattern works only when reversing the entire sequence produces the correct result. It is safe for character-by-character construction but breaks for multi-character chunks that have their own internal order.

Alternative: Use String.join or StringJoiner

For cases where you are assembling parts with a delimiter, String.join or StringJoiner is more readable than manual insertion.

java
1import java.util.List;
2import java.util.StringJoiner;
3
4public class JoinerApproach {
5    public static void main(String[] args) {
6        List<String> parts = List.of("prefix", "middle", "suffix");
7
8        // String.join
9        String result1 = String.join(" -> ", parts);
10        System.out.println(result1); // prefix -> middle -> suffix
11
12        // StringJoiner with prefix and suffix
13        StringJoiner sj = new StringJoiner(", ", "[", "]");
14        sj.add("a").add("b").add("c");
15        System.out.println(sj); // [a, b, c]
16    }
17}

These utilities handle the delimiter logic and produce a single string without any intermediate shifting.

Capacity Management

Pre-allocating capacity reduces buffer resizing but does not eliminate the O(n) shifting cost of insert(0, ...).

java
// Pre-allocate for roughly 200 characters
StringBuilder sb = new StringBuilder(200);
sb.insert(0, "still shifts existing content");

Capacity is relevant when you know the final string length approximately. It prevents the internal array from growing multiple times during construction. But capacity planning and the shifting cost are independent concerns. Addressing one does not fix the other.

ConcernAffected By CapacityAffected By Insert Position
Array reallocationYesNo
Character shiftingNoYes
Final string correctnessNoNo

StringBuilder vs. StringBuffer

StringBuffer has the same API, including insert(0, ...), but is synchronized. In single-threaded contexts (which is almost always the case for string building), StringBuilder is the correct choice because synchronization adds overhead without benefit.

java
1// Single-threaded: use StringBuilder
2StringBuilder sb = new StringBuilder();
3sb.insert(0, "safe and fast");
4
5// Multi-threaded (rare): use StringBuffer
6StringBuffer sbuf = new StringBuffer();
7sbuf.insert(0, "safe but slower");

Common Pitfalls

Searching for a prepend method that does not exist. StringBuilder has append and insert, not prepend. The method you want is insert(0, value).

Assuming prepending is as cheap as appending. Appending is O(1) amortized. Prepending is O(n) per call because existing characters shift. This matters in loops.

Using insert(0, ...) inside a large loop without considering alternatives. For hundreds or thousands of prepends, a Deque plus a single append pass is dramatically faster.

Treating reverse() as a universal workaround. Reversing works for character-level construction but corrupts multi-character chunks. Use it only when you are certain the reversal produces valid output.

Forgetting that insert returns the same StringBuilder. Chaining is possible but can reduce readability, especially with multiple positional inserts.

Summary

  • Prepend with StringBuilder by calling insert(0, value).
  • insert supports strings, primitives, char arrays, and objects, not just String.
  • Occasional prepends are perfectly fine. Repeated prepends in loops have O(n) per-call cost and can become quadratic.
  • For prepend-heavy workloads, collect parts in an ArrayDeque with addFirst and build the final string in one append pass.
  • Pre-allocating capacity reduces buffer resizing but does not affect the shifting cost of front insertions.
  • Choose the approach based on actual workload: insert(0, ...) for simplicity, Deque for performance.

Course illustration
Course illustration

All Rights Reserved.