Skip to content
dsa450for five year olds

String · 4 of 43

Why strings are immutable in Java

A Java String is like a word carved in stone: nobody can change it, so everyone can safely share it, and a change always makes a new stone.

Simple way
time O(n^2)
room O(n)
Clever way
time O(n)
room O(n)

The problem, simply

This is a "why" question that comes up in almost every Java interview. In Java, once a String object is made, its letters can never change. Methods like toUpperCase(), replace() or + never touch the old string. They make a brand new one.

You need to explain why Java was designed this way, and what it means for your code. The code part is the classic trap: building a long string with + in a loop is slow, and StringBuilder is the fix.

A story to remember it

Picture it

graph
s1s2s3"hi""hi"copypoolheap
mapstring pool: one object per literal
"hi"shared by s1 and s2"bye"shared by everyone who writes "bye"

s1 and s2 both point to the one shared hi in the string pool. s3 used new, so it points to its own copy. Nobody can change either object, so sharing is safe.

  • seen
  • done

Examples

A copy keeps the old value

In
String s = 'hi'; String t = s; s = s + '!';
Out
t is 'hi', s is 'hi!'

s + "!" made a new object and moved s to it. t still points to the old, unchanged hi.

Calling a method without saving it

In
String u = 'cat'; u.toUpperCase();
Out
u is still 'cat'

toUpperCase() returned a new string, CAT, and nobody kept it. Write u = u.toUpperCase();.

Same letters, different objects

In
'hi' == 'hi', new String('hi') == 'hi', new String('hi').equals('hi')
Out
true, false, true

Two literals share one pool object, so == is true. new makes a separate object, so == is false. equals compares letters and is true.

Building a long string

In
s = s + 'a', repeated 100000 times
Out
slow: billions of letter copies

Each + copies the whole string so far. With a StringBuilder the same job copies each letter about once or twice.

Way 1: the simple way

Build a string of n letters with + in a loop. Because strings cannot change, every step carves a new stone.

  1. Start with s = "".
  2. Each time, s + "a" makes a new string, copies all old letters into it, and adds one more.
  3. s now points to the new string. The old one is garbage.

Every + carves a new stone

01 / 04

graph
s"""a""aa""aaa"

s starts at the empty string.

letters copied
0
  • looking now
  • answer
  • rejected
  • just added
  • ignored
java
class Solution {
    public String buildString(int n) {
        String s = "";
        for (int i = 0; i < n; i++) {
            s = s + "a";        // a new String every time, old letters copied again
        }
        return s;
    }
}

How long it takes: step k copies k letters, so the total is 1 + 2 + ... + n, which is O(n^2). Extra room: only the newest string and the one being built are alive, O(n), but the loop throws away about n^2 / 2 letters of garbage for the garbage collector to clean up. Modern Java compiles a single a + b + c well, but inside a loop it still makes a new string each time.

Way 2: the clever way

Use a whiteboard that can change: StringBuilder. It keeps a box of letters with spare room at the end.

  1. append writes the next letter into the next free box. No copying.
  2. When the boxes are full, it makes a bigger box (about twice as big) and copies once.
  3. At the end, toString() carves the final stone once.

Because the box doubles, the total copying is a small multiple of n.

The whiteboard writes in place and grows by doubling

01 / 06

arraybuffer
01

new StringBuilder(2) makes a whiteboard with room for 2 letters. It is empty.

length
0
capacity
2
  • seen
  • done
  • answer
  • just added
java
class Solution {
    public String buildString(int n) {
        StringBuilder sb = new StringBuilder(n);   // room for n letters up front
        for (int i = 0; i < n; i++) {
            sb.append('a');                        // write in place, no new String
        }
        return sb.toString();                      // carve the stone once
    }
}

How long it takes: each append is O(1) on average, so the loop is O(n). Extra room: the buffer and the final string, O(n). Giving the size up front, new StringBuilder(n), skips the growing step entirely.

Another way: make your own immutable class

Interviewers often follow up with "write an immutable class". These are the same rules Java uses for String.

java
final class Point {                       // final: nobody can subclass it and add changes
    private final int x;                  // private final: set once, never again
    private final int y;
    private final List<Integer> tags;

    Point(int x, int y, List<Integer> tags) {
        this.x = x;
        this.y = y;
        this.tags = new ArrayList<>(tags);   // copy in, so the caller cannot change it later
    }

    int getX() { return x; }
    int getY() { return y; }
    List<Integer> getTags() {
        return Collections.unmodifiableList(tags);   // read only view out
    }

    Point withX(int newX) {
        return new Point(newX, y, tags);  // a "change" makes a new object, like String
    }
}

Watch out for

Why Java made strings immutable, the four reasons to say in an interview:

  • The string pool. Literals are shared by everyone. Sharing is only safe because nobody can change them.
  • Security. File paths, URLs, class names and database usernames are checked once and then used. If they could change after the check, an attacker could swap them.
  • Threads. Many threads can read the same string with no locks, because nothing can change under them.
  • Hashing. A string caches its hashCode(). That works because the letters never change, which makes strings fast and reliable as HashMap keys.

Common slips:

  • Calling s.trim() or s.replace(...) and not saving the result.
  • Passwords in a String. They stay in memory until garbage collection and may land in the pool. A char[] can be wiped with zeros right after use.
  • Using StringBuffer by habit. It locks on every call. Use StringBuilder unless several threads write to the same builder.

What interviewers ask next

  • Is a String really impossible to change? Normal code cannot change it. Reflection or native code can break into the private array, but that is a hack and recent Java versions block it by default.
  • What does intern() do? It returns the pool copy of a string, adding it to the pool if needed. Then == works between interned strings.
  • How is a String stored? Since Java 9, as a byte[] plus a flag that says Latin-1 (one byte per letter) or UTF-16 (two bytes). This is called compact strings.
  • Why is String a final class? So no subclass can add a method that changes the letters and break all the guarantees above.
  • String, StringBuilder, StringBuffer: when to use which? String for values you keep and share, StringBuilder to build text in one thread, StringBuffer only when threads share one builder.

Remember this

Grown-up words: the stone tag is an immutable object, the shared tag room is the string pool (interning), the whiteboard is a mutable buffer, StringBuilder, and its doubling growth gives amortized O(1) appends.