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
+ works but creates new objects each time.1996 • Java 1.0.2 — StringBuffer Arrives
2004 • Java 5 — StringBuilder Introduced
2017 • Java 9 — Compact Strings & Indify
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.
Immutability of String
String object cannot be modified after creation. Any operation that appears to change it actually returns a brand-new String.Mutability of StringBuilder
StringBuilder holds an internal character array that can grow. You modify its contents in place, avoiding new object creation.Internal Buffer & Capacity
Method Chaining with append()
append() method returns the same StringBuilder reference, letting you chain calls like sb.append("a").append("b").toString() to Finish
sb.toString() to convert the internal buffer into an immutable String you can pass around or print.KEY TAKEAWAY
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.
+ 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
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
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
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
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.
| Method | Description | Example |
|---|---|---|
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
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
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
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
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 containsAlice,Bob,Carol,Dave. - 4
Step 4 — Convert to String
CalltoString()to get the final immutable String.String csvRow = sb.toString(); // "Alice,Bob,Carol,Dave" - 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.
| Feature | String | StringBuilder | StringBuffer |
|---|---|---|---|
| Mutability | Immutable | Mutable | Mutable |
| Thread-safe? | Yes (immutable) | No | Yes (synchronized) |
| Speed | Slow for repeated concat | Fastest | Slightly slower (locking) |
| Best for | Constants, short text, hash keys | Building strings in loops (single thread) | Building strings in multi-threaded code |
| Since Java version | 1.0 | 1.5 (Java 5) | 1.0 |
Comparison of String, StringBuilder, and StringBuffer
WHEN TO USE WHICH
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 Lesson | Advanced Connection |
|---|---|
| Internal char array that doubles in size | Same strategy used by ArrayList, HashMap, and dynamic arrays in other languages |
| Immutability of String | Functional programming relies on immutable data; Java Records and Kotlin's val enforce it |
| O(n²) cost of + in a loop | Algorithm analysis (Big-O notation) — a key topic in AP CS A and data structures courses |
| StringBuffer's synchronized methods | Concurrency and thread safety — critical in systems programming and server-side Java |
| toString() producing a final String | The 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
+ 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
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
+, approximately how many total character-copy operations occur? How many String objects are created?Show answer
toString()).Question 3: INTERMEDIATE
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
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
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
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
+ 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
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.