Why string questions are unavoidable
Strings are the first non-trivial object every Java developer touches, so interviewers use them as a fast probe of how well you understand references, immutability and memory. The questions look simple — "what does == do?" — but each one has a layer underneath that reveals whether you understand objects or just use them. Almost every Java loop opens with at least one string question.
This page covers the string questions that recur at every level, from immutability to the constant pool to efficient building. For the memory-model background behind the pool, the memory management questions are a natural companion.
Q1. Why is String immutable, and what does that buy you?
A String's character data cannot change after construction. This is deliberate: immutability lets the JVM safely share identical string literals in a pool, makes strings safe as HashMap keys and across threads without locking, allows the hash code to be computed once and cached, and prevents a validated string (a file path, a URL) from being altered after a security check.
The most convincing way to answer is to connect immutability to a concrete benefit. Because the content never changes, a string's hashCode() is stable, so caching it makes hash-map lookups faster; and because two threads can never observe a half-modified string, no synchronization is needed. Interviewers love the follow-up "so how do methods like toUpperCase() work if strings are immutable?" — they return a new string; the original is untouched.
Q2. What is the string constant pool?
The string pool is a special region where the JVM stores one canonical copy of each string literal. When you write a literal, the JVM reuses the pooled instance instead of creating a new object, so identical literals share one reference — which is only safe because strings are immutable.
String a = "hello";
String b = "hello";
System.out.println(a == b); // true — both refer to the same pooled literal
String c = new String("hello");
System.out.println(a == c); // false — new String() forces a distinct heap object
System.out.println(a.equals(c)); // true — same content
That contrast is the whole lesson: literals are pooled and share a reference; new String() explicitly builds a separate object on the heap. Knowing why new String("hello") is wasteful — it creates an object and references the pooled literal — is a small detail that reads as fluency.
Q3. Why should you compare strings with equals() and not ==?
== asks "are these the same object?"; equals() asks "do these have the same characters?" You almost always mean the second. Two strings built at runtime can hold identical content yet be different objects, so == returns false while equals() returns true.
The trap appears when literals accidentally make == work — comparing two literals returns true because both are pooled, which lulls people into using == until a runtime-built string breaks it. Use equals() for content, and equalsIgnoreCase() when case should not matter. For null-safety, Objects.equals(a, b) or calling equals on a known-non-null literal ("YES".equals(input)) avoids NullPointerException.
Q4. Why is repeated string concatenation slow, and what do you use instead?
Because String is immutable, every + on strings creates a brand-new string and copies all existing characters. In a loop this is O(n²) work and produces a pile of garbage objects. StringBuilder mutates a single growable buffer, turning the same work into O(n).
// Quadratic: each += builds a new String and copies everything so far
String result = "";
for (String part : parts) result += part; // avoid in loops
// Linear: one mutable buffer, appended in place
StringBuilder sb = new StringBuilder();
for (String part : parts) sb.append(part);
String result2 = sb.toString();
A fair nuance: a single a + b + c on one line is fine — the compiler optimizes it, historically to a StringBuilder and in modern JVMs via invokedynamic string concatenation. The problem is concatenation inside a loop, where the optimization cannot help because a new builder is created each iteration.
Q5. StringBuilder vs StringBuffer — which and why?
Both are mutable buffers with an identical API. StringBuffer synchronizes every method, making it thread-safe but slower; StringBuilder is unsynchronized and faster. Since string building is almost always confined to a single thread (often a single method), StringBuilder is the default, and StringBuffer is effectively legacy.
The honest senior point: even when multiple threads are involved, sharing a single mutable buffer between them is usually a design smell — you would build per-thread and combine, not synchronize on one buffer. So "I use StringBuilder" is the right default, with StringBuffer mentioned only to show you know the difference.
Q6. What does String.intern() do?
intern() returns the pooled reference for a string's content: if an equal string is already in the pool, you get that shared instance; otherwise your string is added to the pool. It lets you deliberately achieve reference equality for equal content, which can save memory when you hold many duplicate strings.
String built = new StringBuilder("he").append("llo").toString();
System.out.println(built == "hello"); // false — distinct heap object
System.out.println(built.intern() == "hello"); // true — canonical pooled instance
Use it sparingly. Interning huge numbers of unique strings just fills the pool and pressures memory; it pays off only with heavy duplication. Being able to say when interning helps — and when it hurts — is what the question is really checking.
Q7. How does the switch statement work with strings?
Since Java 7, switch supports String, matching using hashCode() first to pick a candidate and then equals() to confirm — so it behaves like a content comparison, not reference comparison. A null selector throws NullPointerException, so guard against null before switching.
Modern Java (14+) also offers switch expressions with arrow labels, which are exhaustive and avoid fall-through bugs. Mentioning that a string switch is really "hash then equals under the hood" ties this question back to Q2 and Q3 and shows a consistent mental model.
Q8. How do you reverse a string or check a palindrome correctly?
Use StringBuilder.reverse() for a straightforward reversal; for a palindrome check, compare characters from both ends inward, which avoids allocating a second string. Watch for the Unicode caveat: a naive char-by-char reversal can break characters made of surrogate pairs or combining marks.
String reversed = new StringBuilder(input).reverse().toString();
boolean isPalindrome(String s) {
int i = 0, j = s.length() - 1;
while (i < j) if (s.charAt(i++) != s.charAt(j--)) return false;
return true;
}
The two-pointer palindrome check is preferred in coding rounds because it is O(n) time and O(1) extra space. Noting the Unicode edge case — that char is a UTF-16 code unit, not always a full character — is the detail that separates a careful answer from a copy-pasted one.
How to prepare
Anchor everything to one idea: strings are immutable, and every other behaviour — pooling, safe hashing, thread safety, why concatenation is slow — falls out of that. Write the three demos yourself: the == versus equals surprise, the quadratic loop rewritten with StringBuilder, and the intern() reference test. Each takes a few minutes and makes the answer muscle memory. Pair this with the generics questions for type-safety fundamentals and the memory management set for where the pool lives, then rehearse under follow-up pressure in a mock interview.
Frequently Asked Questions
Why is String immutable in Java?
What is the difference between == and equals() for strings?
When should I use StringBuilder instead of String concatenation?
What is the difference between StringBuilder and StringBuffer?
What does String.intern() do?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Java Full Stack program

