This question tests two separate things and a strong answer separates them: the memory optimization (interning) and the equality semantics trap (==). The interviewer wants to know you understand both the mechanism and the footgun.
The mechanism. Interning means storing a canonical copy of a string in a runtime-wide table, so that equal strings share one object. Two interned strings that are equal are also identical: the same address. Interning is a memory optimization — one copy instead of N — and it turns equality checks into pointer comparisons. The compiler and runtime intern string literals: the same literal in source compiles to a reference to one table entry, which is why Java's "hello" == "hello" is true. What is not interned: runtime-constructed strings. "hello" + " world" in Java may or may not compile to a constant; a variable concatenation, new String("hello"), or reading from I/O always produces a fresh object.
The footgun: == on strings compares references, not content, in Java, C#, Python (identity vs equality aside — Python's is), and most languages where strings are objects. The classic failure:
String a = "hi"; // interned literal
String b = new String("hi"); // fresh heap object
a == b // false — different addresses
a.equals(b) // true — same contentIn C++, std::string is a value type so == compares contents — but const char* comparison is pointer equality, same trap with different syntax. In C#, == is overloaded for string to compare content, while ReferenceEquals exposes the identity — so the trap is language-dependent, which is exactly what the interviewer is probing.
Edge cases worth naming:
- The intern pool is a table with a lock; the JVM's
String.intern()has a fixed table size and historically caused contention and memory leaks — manual interning is a measured decision, not a default. - Dedup at GC time: some runtimes (Java G1 with
-XX:+UseStringDeduplication) deduplicate strings during collection as a cheaper alternative to eager interning. - Substrings share buffers in some implementations:
s.substring(0, 3)kept a reference to the whole original array, so a small substring could pin a large string in memory — a real production leak pattern. - Concat vs literals:
"a" + "b"where both are constants is folded at compile time and interned;s + "b"allocates a new string every iteration — the string-concat trace shows the heap growth.
A strong closing: "Interning buys one copy and pointer equality, but it only happens when the runtime says so — literals yes, runtime strings no — so never rely on == for content."