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.

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.

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.

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.

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.

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 + 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 (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
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.

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.

Most-used StringBuilder methods
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();
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.

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
Step 1 — Set up the dataStart with the array: String[] names = {"Alice", "Bob", "Carol", "Dave"}; We want the output to be "Alice,Bob,Carol,Dave".
2
Step 2 — Create the StringBuilderInstantiate 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
Step 3 — Loop and appendIterate 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
Step 4 — Convert to StringCall toString() to get the final immutable String.
String csvRow = sb.toString(); // "Alice,Bob,Carol,Dave"
5
Step 5 — Analyze object creationWe 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

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.

Comparison of String, StringBuilder, and StringBuffer
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
🔑 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.

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.

Practice Problems

PROBLEM 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?
PROBLEM 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?
PROBLEM 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.
PROBLEM 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.
PROBLEM 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.

Lesson 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)