Why are strings immutable in many languages?
Updated Feb 20, 2026
Short answer
Immutable strings can be shared freely without defensive copying, cached safely as hash-map keys, and passed between threads without synchronisation — because nothing can change them after creation. The trade-off is that every modification allocates a new string, which makes concatenation in a loop quadratic unless you use a builder.
Deep explanation
Java, C#, Python, JavaScript, and Go all make strings immutable. The reasons compound:
- Safe sharing. Any number of references can point at one string with no risk that one holder mutates it under another. Without this, every method receiving a string would need a defensive copy to be safe.
- Hash caching. A string's hash can be computed once and cached, because it can never change. This is what makes strings efficient hash-map keys — and it is why a mutable key is dangerous: mutate it after insertion and the entry becomes unreachable in its original bucket.
- Thread safety for free. Immutable data needs no locks. Multiple threads read the same string concurrently with no synchronisation and no data races.
- Interning. Identical literals can share one instance, cutting memory in programs that manipulate many repeated strings.
- Security. A file path or SQL fragment validated at a boundary cannot be altered afterwards by another reference — closing a whole class of time-of-check-to-time-of-use bugs.
The cost. Every "modification" allocates:
# O(n²) — each += builds a whole new strings = ''for chunk in chunks: # 10,000 iterations s += chunk # copies everything so far, every time
# O(n) — collect, then join onces = ''.join(chunks)At 10,000 chunks the first form copies roughly 50,000,000 characters; the second copies each character once. This is the single most common performance bug arising from string immutability, and every language provides the escape hatch: StringBuilder in Java and C#, strings.Builder in Go, ''.join() in Python.
CPython optimises some += cases when the string has exactly one reference, which makes the quadratic behaviour intermittent and therefore harder to spot.
Real-world example
Immutability is why this is safe:
config = {'/etc/app.conf': parsed_settings} # string key, hash cached
path = '/etc/app.conf'validate(path) # cannot be tampered with after validationload(path) # guaranteed to be the same path validated aboveAnd why a mutable key is not. In a language with mutable strings, another reference could rewrite path between validate and load, or mutate a key already stored in the map and orphan its entry — the dictionary would keep the old hash bucket while the key now hashes elsewhere.
Common mistakes
- - Building strings with `+=` in a loop, which is O(n²)
- use a builder or join.
- - Assuming a method like `.upper()` or `.strip()` mutates in place — they return a new string, and ignoring the return value is a silent no-op.
- - Relying on `is` / reference equality for string comparison. Interning makes it work for literals and then fail for computed strings.
- - Treating immutability as a guarantee of secrecy — the old value stays in memory until garbage collected, which is why sensitive data belongs in a mutable char array that can be zeroed.
- - Assuming all languages agree
- C strings and Ruby strings are mutable, and code ported between them can break in subtle ways.
Follow-up questions
- Why does string concatenation in a loop become O(n²)?
- What is string interning?
- Why are passwords recommended to be held in char arrays rather than strings?
- How does immutability help concurrency?