Why does String immutability give thread-safety for free and allow the hash code to be cached?
answer
- No writes after construction = no race to guard
- Inherently thread-safe, shareable without locks
- hashCode computed once, cached in a field
- Stable hash+equals = safe HashMap key
- Hash cache = benign data race (same value recomputed)
basics
~20 sSince a String never changes, many threads can read it at once with no locks — there is nothing to corrupt. And because its characters are fixed, its hash code is computed once and stored, so repeated hashing (e.g. as a HashMap key) is cheap and consistent.
solid answer
~50 sThread-safety problems come from concurrent writes racing with reads. An immutable String has no writes after construction, so any number of threads can read it simultaneously without synchronization, locks, or visibility concerns — it is inherently thread-safe and freely shareable. Immutability also enables hash caching: String stores its computed hashCode in a field and returns the cached value on later calls, which is only correct because the contents (and thus the hash) can never change. This makes Strings ideal HashMap/HashSet keys: their hashCode and equals are stable for life, so a key placed in a bucket can always be found again. If a key's hash could change after insertion, lookups would silently fail. One subtlety: the hash cache uses a benign data race — multiple threads may recompute the same value, but since they all compute the identical immutable result, it is safe without a lock.
go deeper
Knows immutable strings can be shared between threads safely and make good map keys.
Explains that no post-construction writes means no race, and that the hash is cached because contents are fixed.
Articulates the HashMap key contract, the O(n)->O(1) hash caching, and the benign data race on the hash field.
Reasons about the JMM guarantees (atomic int write, no safe-publication issue for the cached value) and immutable-object design as a concurrency strategy.
## Where thread-safety problems come from A **race condition** happens when two threads access shared data and at least one of them **writes**, with no coordination — the result depends on timing and can be corrupt. The fix is usually synchronization (locks), which costs performance and adds complexity. An **immutable** object has *no writes after construction*. There is simply nothing for a writer to race against. Therefore any number of threads can read an immutable String at the same time with **no locks, no `synchronized`, no `volatile`** needed. This is what 'thread-safe for free' means: you get safety as a property of the design, not by adding machinery. You can pass a String to other threads, store it in shared collections, and cache it globally without defensive copies. ## Hash code caching `String.hashCode()` is computed from the characters using a fixed formula (`s[0]*31^(n-1) + ... + s[n-1]`). Computing it walks the whole string — O(n). Since the characters never change, the result never changes either, so `String` computes it **once** and stores it in a private `int hash` field; subsequent calls return the cached value in O(1). ```java // conceptually inside String private int hash; // cached, 0 until first computed public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { h = /* compute from chars */; hash = h; // cache it } return h; } ``` This caching is only **correct because the string is immutable** — if contents could change, the cached hash would go stale and break every hash-based collection. ## Why this matters for HashMap keys A `HashMap` places a key in a bucket chosen by its `hashCode()`, and finds it again by recomputing the hash and using `equals()`. The contract requires that a key's hash and equality **don't change while it's in the map**. Immutable Strings satisfy this perfectly: their hash and equals are fixed for life, so a key is always found in the bucket it was stored in. Use a *mutable* object as a key and change it after insertion, and the map will look in the wrong bucket — the entry becomes unreachable. Immutability makes String the safest possible map key. ## The benign data race subtlety (senior+) The hash field is **not** `volatile` or guarded by a lock. Under concurrency, two threads might both see `hash == 0` and both compute it. That's fine: they compute the *same* value (the contents are immutable), and `int` writes are atomic, so the worst case is a little redundant work, never a wrong answer. This is a textbook **benign data race** — safe precisely because the computed result is invariant.
- Why is a mutable object a dangerous HashMap key?If its fields change after insertion, its hashCode/equals change, so the map looks in the wrong bucket and can no longer find the entry — it effectively leaks. Immutable keys avoid this.
- Is String's hash cache thread-safe without volatile?Yes. It is a benign data race: concurrent threads may recompute the hash, but since contents are immutable they all produce the same value, and int writes are atomic, so no incorrect result occurs.
saying these in an interview costs you the question
- Saying you still need to synchronize reads of a String
- Thinking the cached hash could become stale
- Believing any object is a fine map key regardless of mutability
- Claiming the hash field must be volatile to be correct