Back to String & String Pool
Q7Question 7 of 12

How do String and StringBuffer handle memory differently?

Key Concepts
  • String: immutable, each modification creates a new object on the heap
  • StringBuilder/Buffer: mutable, modifies an internal array in place
  • StringBuilder starts with capacity 16, doubles when full
  • String concatenation in a loop creates N objects; StringBuilder creates 1
  • String objects eligible for String Pool sharing; StringBuilder is not pooled

Definition

String creates a new object for every change. StringBuilder/Buffer modifies a resizable internal array in place, avoiding repeated allocation.

Memory differences:

DIAGRAM
- **String:** New object per modification. `"a" + "b"` → new String("ab"). In a loop, N concatenations = N objects.
- **StringBuilder:** Modifies an internal char/byte array. Starts at capacity 16. Doubles when full. One object for all appends.
- **StringBuffer:** Same memory model as StringBuilder, with synchronized methods.

String: printing a new page every time you add a word. StringBuilder: writing on a notepad that grows when you reach the end. The notepad is reused; the printed pages are discarded.

Understanding the Concepts

Memory in a loop

JAVA
// String: ~10,000 intermediate objects
String s = "";
for (int i = 0; i < 10_000; i++) {
    s += i;  // new String + new StringBuilder per iteration
}
// Memory: 10,000 garbage Strings + 10,000 garbage StringBuilders

// StringBuilder: 1 object, resized ~7 times (16→32→64→128→256→512→1024→2048...)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10_000; i++) {
    sb.append(i);  // modifies in place
}
String result = sb.toString();  // 1 final String

Capacity and resizing

JAVA
StringBuilder sb = new StringBuilder();  // default capacity 16
System.out.println(sb.capacity());  // 16

sb.append("12345678901234567");  // exceeds 16 → resize
System.out.println(sb.capacity());  // 34 (old*2 + 2)

// Pre-size to avoid resizing
StringBuilder sb2 = new StringBuilder(1024);  // start with 1024

What should a senior engineer know?

  • Pre-sizing StringBuilder avoids resizing overhead: new StringBuilder(estimatedLength).
  • StringBuilder.trimToSize() reduces capacity to match content length. Frees unused internal array space.
  • String objects in the pool are never GC'd (unless the classloader is unloaded). Heap Strings are GC'd normally.

What are common mistakes?

A frequent mistake is using == to compare String content instead of equals().

Wrong approach:

JAVA
package com.bank.service;

public class AccountValidator {
    public boolean validateAccountType(String type) {
        // WRONG: == compares references, not content
        if (type == "SAVINGS") {
            return true;
        }
        return false;
    }
}

Correct approach:

JAVA
package com.bank.service;

public class AccountValidator {
    public boolean validateAccountType(String type) {
        // CORRECT: equals() compares content
        if ("SAVINGS".equals(type)) {  // null-safe: literal first
            return true;
        }
        return false;
    }
}

Using the literal on the left side of equals() prevents NullPointerException when the variable is null. This is a standard practice at National Bank of India for all string comparisons in service code.

What are the edge cases?

String behavior has several surprising edge cases that can catch banking developers off guard.

JAVA
package com.bank.edge;

public class StringEdgeCases {
    public static void main(String[] args) {
        // Edge case 1: compile-time constant folding
        String a = "SAV" + "2001";       // Compile-time constant
        String b = "SAV2001";
        System.out.println(a == b);       // true (same pool entry)

        // Edge case 2: variable concatenation is NOT pooled
        String prefix = "SAV";
        String c = prefix + "2001";       // Runtime concatenation
        System.out.println(b == c);       // false (different objects)

        // Edge case 3: final variable IS a compile-time constant
        final String finalPrefix = "SAV";
        String d = finalPrefix + "2001";  // Compile-time constant
        System.out.println(b == d);       // true (same pool entry)

        // Edge case 4: substring no longer shares char array (Java 7u6+)
        String full = "AccountNumber:SAV2001";
        String sub = full.substring(14); // New String, new char array
    }
}
TEXT
true
false
true

The final keyword makes the variable a compile-time constant, which changes how the compiler handles concatenation.

What are common interview follow-up questions?

Interviewers often follow up with these related questions:

  • How does this concept apply to a high-throughput banking transaction system?
  • What are the performance implications in a production environment?
  • How would you debug issues related to this concept?
  • What tools would you use to monitor this in production?
  • How does this behave differently in Java 8 vs Java 17+?

When answering follow-up questions, always relate your answer back to practical experience. For example, at National Bank of India, Rajesh debugged a production issue related to this concept by using JFR recordings and heap dumps. Providing concrete examples from banking systems shows the interviewer that you understand both the theory and the practice.

Visual Overview

DIAGRAM
  String: s += "x" × N
    → N new String objects → N garbage → GC pressure

  StringBuilder: sb.append("x") × N
    → 1 object, resizable array → minimal GC
    → capacity: 16 → 34 → 70 → 142 → ... (doubles)

  Pre-size: new StringBuilder(1024) → avoid resizing

Key Takeaway

String creates a new object for every modification. StringBuilder modifies an internal resizable array in place. In a loop, String concatenation creates N garbage objects while StringBuilder uses one. Pre-size StringBuilder to avoid resizing overhead.

One sentence to remember: String allocates per change, StringBuilder modifies in place – pre-size StringBuilder for best performance.

---

Share
Use ← → arrow keys to navigate