Java • Syntax and Core Types

StringBuilder — Use StringBuilder for efficient concatenation (intro)

Learn why joining strings with + inside loops wastes memory and how StringBuilder fixes the problem.

Java programming emblem with a coffee cup, code braces, and terminal
Inside this lesson 10 sections 5 questions 8 figures

SECTION 1

Historical Context & Motivation

From the very first version of Java in 1995, the String class was designed to be immutable, meaning that once a String object is created, its characters can never change. This design choice made Strings safe to share between different parts of a program, but it introduced a hidden cost: every time you "change" a String — by appending, replacing, or trimming — Java actually creates an entirely new String object in memory. The old one sits around unused until the garbage collector reclaims it.

For a single concatenation, that overhead is negligible. But imagine building a long message inside a loop that runs thousands of times. Each iteration allocates a new String, copies all the old characters plus the new ones, and discards the previous object. Developers quickly noticed that programs doing heavy string work became sluggish. Java needed a mutable alternative that could grow a character sequence in place without creating throwaway copies.

1995 • Java 1.0 — Immutable Strings

Java launches with the immutable String class. Concatenation with + works but creates new objects each time.

1996 • Java 1.0.2 — StringBuffer Arrives

StringBuffer is introduced as a thread-safe, mutable character sequence. It solves the performance problem but carries synchronization overhead.

2004 • Java 5 — StringBuilder Introduced

StringBuilder is added as a faster, non-synchronized alternative to StringBuffer. It quickly becomes the recommended choice for single-threaded string building.

2017 • Java 9 — Compact Strings & Indify

The JVM uses invokedynamic to optimize simple + concatenation, but StringBuilder remains essential inside loops.

The core question this lesson addresses is simple: how can we build strings efficiently in Java without creating mountains of temporary objects? The answer is StringBuilder.

SECTION 2

Core Principles & Definitions

Before diving into code, you need to understand a few foundational ideas that explain why StringBuilder exists and how it differs from a regular String. These principles will help you decide when to reach for StringBuilder in your own programs.

1

Immutability of String

A String object cannot be modified after creation. Any operation that appears to change it actually returns a brand-new String.
2

Mutability of StringBuilder

A StringBuilder holds an internal character array that can grow. You modify its contents in place, avoiding new object creation.
3

Internal Buffer & Capacity

StringBuilder starts with a default capacity of 16 characters. When you exceed it, the buffer doubles (roughly), so resizing happens infrequently.
4

Method Chaining with append()

The append() method returns the same StringBuilder reference, letting you chain calls like sb.append("a").append("b").
5

toString() to Finish

When you are done building, call sb.toString() to convert the internal buffer into an immutable String you can pass around or print.
✦

KEY TAKEAWAY

Think of a String like writing on a whiteboard that is immediately laminated — you cannot erase or add to it, so you need a whole new board every time. A StringBuilder is like writing on a dry-erase whiteboard: you can keep adding text, erasing, and rewriting on the same surface. When you are satisfied, you take a photo (call toString()) to freeze the result.

SECTION 3

Visual Explanation — String vs. StringBuilder in Memory

The diagram below contrasts what happens in memory when you concatenate three words using the + operator on Strings versus using a StringBuilder. Notice how the String approach creates intermediate objects that are immediately discarded, while StringBuilder modifies a single buffer.

String Concatenation vs. StringBuilderUsing String + operatorString s = "";""← obj #1s = s + "Hello";""garbage"Hello"← obj #2s = s + " World";"Hello"garbage"Hello World"← obj #3s = s + "!";"Hello World"garbage"Hello World!"← obj #4 (final)4 objects created, 3 discardedUsing StringBuilderStringBuilder sb = new StringBuilder();[ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ]cap=16sb.append("Hello");[H e l l o _ _ _ _ _ _ _ _ _ _ _ ]sb.append(" World");[H e l l o W o r l d _ _ _ _ _ ]sb.append("!");[H e l l o W o r l d ! _ _ _ _ ]sb.toString();"Hello World!"← final String1 buffer + 1 final String = 2 objects
Left: Each + operation creates a new String object, leaving the old one as garbage. Right: StringBuilder modifies the same internal buffer, then produces one final String via toString().

On the left side of the diagram, every concatenation produces a fresh String object while the previous one becomes garbage. After just three appends you have created four objects and discarded three. On the right side, StringBuilder writes into the same character array. Only at the end does toString() produce a single new String. The difference becomes dramatic when the loop runs hundreds or thousands of times.

SECTION 4

How StringBuilder Works Internally

Under the hood, a StringBuilder maintains a char array (in modern JVMs, a byte[]) and an integer count tracking how many characters are in use. When you call append(), the new characters are copied into the array starting at index count, and count is increased. If the array is too small, it allocates a larger one — typically double the old capacity plus two — and copies the existing characters over.

NEW CAPACITY FORMULA

newCapacity=oldCapacity×2+2newCapacity = oldCapacity × 2 + 2

This growth strategy is called amortized doubling. It means resizing happens less and less often as the buffer grows, keeping the average cost of each append very low.

OBJECTS CREATED — STRING + IN A LOOP

objects≈n+1(fornconcatenations)objects ≈ n + 1 (for n concatenations)

Each iteration creates one new String, plus the initial empty String. With StringBuilder, you create roughly 2 objects total (the builder and the final String), regardless of how many appends you perform.

CHARACTERS COPIED — STRING + IN A LOOP

totalcopies≈n×(n+1)/2→O(n2)total copies ≈ n × (n + 1) / 2 → O(n²)

If you append one character at a time for n iterations, the first copy moves 1 character, the second moves 2, and so on. The sum 1 + 2 + … + n = n(n + 1)/2, which grows quadratically. StringBuilder's amortized cost is only O(n).

💡

Default Capacity Tip

If you already know roughly how many characters you will need, pass that number to the constructor: new StringBuilder(500). This avoids repeated buffer resizing and is a simple optimization.

SECTION 5

Key StringBuilder Methods

StringBuilder offers more than just append(). The table below lists the methods you will use most often, along with brief descriptions and examples. All of these methods modify the same StringBuilder object in place and return a reference to it, enabling method chaining.

MethodDescriptionExample
append(x)Adds x (String, int, char, etc.) to the end of the buffer.sb.append(42) // "...42"
insert(index, x)Inserts x at the given index, shifting existing characters right.sb.insert(0, "Hi ") // prepend
delete(start, end)Removes characters from index start (inclusive) to end (exclusive).sb.delete(0, 3)
replace(start, end, str)Replaces the specified range with the given string.sb.replace(0, 5, "Hey")
reverse()Reverses the entire character sequence in place.sb.reverse() // "olleH"
toString()Returns an immutable String snapshot of the current buffer contents.String result = sb.toString();
length()Returns the number of characters currently stored (not the capacity).int len = sb.length();

Most-used StringBuilder methods

StringBuilder Method Chaining Flownew StringBuilder()[ ] len=0 cap=16.append.append("Java")[J a v a] len=4.append.append(" is")[J a v a i s] len=7.insert.insert(4, " really")[J a v a r e a l l y i s].append.append(" fun!")[J a v a r e a l l y i s f u n !].toString()"Java really is fun!"Immutable String — ready to useEvery colored box above is the SAME StringBuilder object — only the final green box is a new String.
This flow shows how append() and insert() modify the same object. The final toString() is the only step that creates a new immutable String.

Notice that you can mix different method calls in a single chain. The key point is that every colored box in the diagram represents the same StringBuilder instance — its internal buffer simply grows to accommodate new content.

SECTION 6

Worked Example — Building a CSV Row

Suppose you have an array of student names and need to build a single comma-separated line for a CSV file. Let's walk through the solution step by step using StringBuilder.

Build a CSV row from an array of names

  1. 1

    Step 1 — Set up the data

    Start with the array: String[] names = {"Alice", "Bob", "Carol", "Dave"}; We want the output to be "Alice,Bob,Carol,Dave".
  2. 2

    Step 2 — Create the StringBuilder

    Instantiate a StringBuilder. Since we know each name is roughly 5 characters and we have 4 names plus 3 commas, we can estimate about 23 characters: StringBuilder sb = new StringBuilder(24); — this avoids resizing.
    StringBuilder sb = new StringBuilder(24);
  3. 3

    Step 3 — Loop and append

    Iterate through the array. For each name after the first, append a comma before the name. This avoids a trailing comma. for (int i = 0; i < names.length; i++) { if (i > 0) sb.append(","); sb.append(names[i]); } After this loop the internal buffer contains Alice,Bob,Carol,Dave.
  4. 4

    Step 4 — Convert to String

    Call toString() to get the final immutable String.
    String csvRow = sb.toString(); // "Alice,Bob,Carol,Dave"
  5. 5

    Step 5 — Analyze object creation

    We created exactly 2 objects: the StringBuilder and the final String. If we had used + inside the loop, we would have created roughly 7 intermediate String objects (an empty string plus one per concatenation step).
    StringBuilder: 2 objects vs. String +: ~7 objects

SECTION 7

String vs. StringBuilder vs. StringBuffer

Java gives you three major ways to work with character data. Choosing the right one depends on whether the text changes, whether multiple threads are involved, and how performance-sensitive your code is.

FeatureStringStringBuilderStringBuffer
MutabilityImmutableMutableMutable
Thread-safe?Yes (immutable)NoYes (synchronized)
SpeedSlow for repeated concatFastestSlightly slower (locking)
Best forConstants, short text, hash keysBuilding strings in loops (single thread)Building strings in multi-threaded code
Since Java version1.01.5 (Java 5)1.0

Comparison of String, StringBuilder, and StringBuffer

🔑

WHEN TO USE WHICH

Use String for text that won't change. Use StringBuilder whenever you are building a string piece by piece — especially inside loops. Use StringBuffer only if multiple threads need to modify the same builder. In most high school and introductory programs, StringBuilder is the right choice.

SECTION 8

Connection to Advanced Topics

Understanding StringBuilder is your entry point into broader ideas about mutable versus immutable data structures, memory management, and algorithm complexity. The table below maps concepts from this lesson to more advanced topics you may encounter in AP Computer Science or college courses.

This LessonAdvanced Connection
Internal char array that doubles in sizeSame strategy used by ArrayList, HashMap, and dynamic arrays in other languages
Immutability of StringFunctional programming relies on immutable data; Java Records and Kotlin's val enforce it
O(n²) cost of + in a loopAlgorithm analysis (Big-O notation) — a key topic in AP CS A and data structures courses
StringBuffer's synchronized methodsConcurrency and thread safety — critical in systems programming and server-side Java
toString() producing a final StringThe Builder design pattern — used throughout Java libraries (e.g., Stream, HttpRequest)

As you advance, you will see that the principles behind StringBuilder — amortized resizing, mutable vs. immutable trade-offs, and builder patterns — reappear in nearly every area of software engineering.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain in your own words why using the + operator to concatenate strings inside a loop is inefficient. What happens in memory each time the loop executes a line like result = result + word?
Show answer
Because String is immutable, result + word cannot modify the existing result object. Instead, Java creates a brand-new String that contains all the characters from result followed by all the characters from word. The old result String is no longer referenced and becomes garbage. On each iteration, every character accumulated so far must be copied again, leading to O(n²) total character copies for n iterations.

Question 2: BASIC CALCULATION

If you start with an empty String and concatenate one character in each iteration of a loop that runs 100 times using +, approximately how many total character-copy operations occur? How many String objects are created?
Show answer
The total character copies equal 1 + 2 + 3 + … + 100 = 100 × 101 / 2 = 5,050 character-copy operations. The number of String objects created is 101: the initial empty String plus one new String per iteration. With StringBuilder, you would perform roughly 100 character copies (one per append) and create only 2 objects total (the StringBuilder and the final String from toString()).

Question 3: INTERMEDIATE

Write a method String repeatChar(char c, int n) that returns a String containing the character c repeated n times. Use StringBuilder and set an appropriate initial capacity.
Show answer
A clean solution is: public static String repeatChar(char c, int n) { StringBuilder sb = new StringBuilder(n); for (int i = 0; i < n; i++) { sb.append(c); } return sb.toString(); } Passing n to the constructor sets the initial capacity to exactly the size we need, so the internal array never has to resize. The loop appends one character at a time, and toString() freezes the result.

Question 4: APPLIED

You are given an array int[] scores = {95, 87, 73, 100, 68}. Write code using StringBuilder to produce the formatted output "[95 | 87 | 73 | 100 | 68]". Pay attention to the delimiters and brackets.
Show answer
Here is one approach: int[] scores = {95, 87, 73, 100, 68}; StringBuilder sb = new StringBuilder("["); for (int i = 0; i < scores.length; i++) { if (i > 0) sb.append(" | "); sb.append(scores[i]); } sb.append("]"); String output = sb.toString(); We start the StringBuilder with "[" so the opening bracket is already in place. Inside the loop, we prepend the delimiter " | " before every score except the first. After the loop, we append "]". The result is exactly "[95 | 87 | 73 | 100 | 68]". This pattern — checking if (i > 0) before a delimiter — is the standard way to avoid a leading or trailing separator.

Question 5: CRITICAL THINKING

A classmate argues: "The Java compiler already converts + concatenation into StringBuilder calls, so I never need to use StringBuilder myself." Is this statement fully correct? Explain when the compiler optimization does and does not solve the problem, and give a short code example where manually using StringBuilder is still necessary.
Show answer
The classmate is partially correct. For a single line like String s = a + b + c;, the compiler (or the JVM since Java 9) can optimize this into efficient code. However, if the + appears inside a loop — for example for (String w : words) result = result + w; — the compiler typically creates a new StringBuilder on every iteration instead of reusing one across iterations. This means you still get O(n²) behavior. Manually creating a StringBuilder before the loop and calling append() inside it guarantees O(n) performance. The rule of thumb: for a single expression, trust the compiler; for loops, write your own StringBuilder.

SUMMARY

Lesson Summary

Summary

Java's String class is immutable, which means every concatenation with + creates a new object and copies all existing characters. Inside loops, this leads to O(n²) character copies and a pile of garbage objects. StringBuilder solves this problem by maintaining a mutable internal buffer that grows via amortized doubling, keeping the total work at O(n).

The core workflow is: create a StringBuilder (optionally with an estimated capacity), call append() or insert() as many times as needed, and finish with toString() to freeze the result into an immutable String. Use StringBuffer only when thread safety is required. For single-line expressions outside loops, the + operator is fine — the compiler optimizes it automatically.

Varsity Tutors • Java • StringBuilder — Use StringBuilder for efficient concatenation (intro)