Articles
Java String Interview Questions: Pooling, Equality, and Concatenation Costs
Prepare Java String interview answers with pooling and equality predictions, intern reference traces, growing-loop costs, and a fair benchmark design.
Java String interview questions often look like memory puzzles: two values print the same text, yet one == check succeeds and another fails. The useful answer separates three issues: string content, object identity, and how the value was constructed.
Use the language specification for semantic guarantees and measurements for performance claims. A literal, a constant expression, and runtime concatenation do not have identical identity rules. A short + expression and a loop that repeatedly copies a growing prefix do not have the same cost model either.
This guide uses the Java SE 26 specification and API references for explicit version scope. The original prediction exercise is documentation-reviewed; the copy-count model was executed separately in Python. No JVM benchmark or employer question frequency is claimed.
What does the string pool actually guarantee?
The official JLS string-literal section specifies shared interned instances for literals and string-valued constant expressions. That is a guarantee about particular constructions, not a statement that every equal string in your application already has the same identity.
Think of == as asking whether two references identify the same object. String.equals asks about the string's value. Our interview recommendation is to name the question before giving the result: “These contain equal text, but I must check the construction rule before claiming identical references.”
Do not describe the pool as a separate place outside all heap management or promise that interned strings live forever. Those claims concern an implementation and its reachability behavior, not the language rule needed to answer this exercise.
Likewise, the source-code presence of a literal does not mean every object built from it is that literal's canonical object. A constructor call, a runtime expression, and an explicit interning call need separate explanations. Draw arrows from references to objects; writing the same letters in two boxes is not enough to establish one object.
Predict six results before running the program
Here is our original exercise. Predict each line, then explain the construction responsible for the result. The outputs below are specification-derived expectations, not a claimed recording from a JVM run.
String literal = "sample";
String folded = "sam" + "ple";
final String constantPrefix = "sam";
String ordinaryPrefix = "sam";
String fromConstant = constantPrefix + "ple";
String fromRuntime = ordinaryPrefix + "ple";
String copy = new String(literal);
System.out.println(literal == folded);
System.out.println(literal == fromConstant);
System.out.println(literal == fromRuntime);
System.out.println(literal.equals(fromRuntime));
System.out.println(literal == copy);
System.out.println(literal == copy.intern());
| Comparison | Expected result and reason |
|---|---|
literal == folded | true; the concatenation is a string-valued constant expression |
literal == fromConstant | true; the prefix is a constant variable initialized with a constant expression |
literal == fromRuntime | false; this concatenation is not a constant expression |
literal.equals(fromRuntime) | true; both contain the same sequence |
literal == copy | false; construction creates a distinct String object |
literal == copy.intern() | true; the returned canonical value is the existing literal |
The official JLS concatenation rules specify a newly created result unless the expression is constant. An optimizer must preserve observable language behavior; recognizing a predictable value does not license changing this identity result.
A useful follow-up changes constantPrefix to final String constantPrefix = readPrefix();. The constant-variable definition requires more than final: the initializer must itself be a constant expression. A runtime method call does not qualify merely because it returns the same letters.
This is also why “effectively final” is not the answer. It can matter for capture in a lambda, but it does not turn this ordinary local variable into a compile-time constant variable.
What changes when you call intern?
The official String API defines intern() as returning a canonical representation. Equal strings have the same interned representative. Calling it does not rewrite every reference that already points to the receiver.
In the exercise, copy.intern() returns the canonical object while copy still refers to the constructed object. Assigning copy = copy.intern() would change that local reference. It would not mutate the content or automatically update another variable that retained the original object.
Our proposed whiteboard test adds String alias = copy before that assignment. Explain which reference changes and which remains. This small extension reveals whether you understand reference assignment rather than memorizing a pool diagram.
Use ordinary value comparison when the application requirement is equal text. Interning just to make == work introduces a canonicalization decision that the business requirement may not need. If someone proposes interning repeated tokens to reduce memory, ask about token cardinality, lifetime, throughput, and measured memory behavior.
Those questions do not establish that interning is always expensive or always beneficial. They identify the evidence needed for the proposed workload. Keep the semantic guarantee separate from a speculative memory optimization.
How do equals, nulls and builders differ?
String.equals is a value comparison with another String. It is not a universal text comparison across arbitrary objects. A builder containing the same visible letters should not be assumed equal to a String or another builder through its inherited equality behavior.
The official StringBuilder API describes a mutable sequence and does not override equals. When the requirement is to compare produced String values, obtain the values intentionally, then compare them. Do not confuse a mutable construction object with the immutable result.
StringBuilder builder = new StringBuilder("sam");
String before = builder.toString();
builder.append("ple");
String after = builder.toString();
// before contains "sam"; after contains "sample".
// Mutating builder does not change before.
The example is specification-reviewed. It gives a useful aliasing question: if a function returns the builder instead, who can change its contents? Your API choice determines whether callers receive mutable shared construction state or a finished value.
For nullable String inputs, Objects.equals handles null references. But deciding that two nulls are acceptable is still a domain decision. An omitted user identifier may be invalid even when two absent values compare equal.
Our rehearsal recommendation is to test equal values in distinct objects, unequal values, one null, and two nulls. That set challenges the intended contract. It is more informative than testing only two literals, where identity sharing can accidentally conceal a == bug.
Why is a concatenation expression different from a growing loop?
Start with a readable expression such as "user=" + userId + ",status=" + status. You have a bounded set of parts. The JLS permits implementations to avoid intermediate strings, and the official StringConcatFactory API describes machinery commonly used for invokedynamic concatenation sites.
That does not establish the exact allocation count for every compiler and runtime, nor prove that rewriting every short expression with a builder makes it faster. An interview answer should distinguish an implementation mechanism from a measured result.
Now consider our original loop:
String result = "";
for (String part : parts) {
result += part;
}
Under a straightforward prefix-copy model, each iteration copies the previous accumulated content plus the new part. For n parts of four code units each, the materialized lengths are 4, 8, 12, and so on. Their sum is 2*n*(n+1).
We executed that arithmetic model: 1,000 parts imply 2,002,000 copied code units, while the final result contains 4,000. Doubling to 2,000 parts gives 8,004,000 modeled copies and an 8,000-unit final result. These are illustrative counts, not observed JVM allocations or timings.
The point is the repeated growth pattern. If part lengths vary, sum the cumulative lengths rather than blindly applying the fixed-size formula. A single very long first part is then repeatedly carried forward, which can make an otherwise small record count costly.
What does a builder improve, and what remains to test?
A conventional alternative keeps one mutable construction buffer and produces the final value once:
StringBuilder result = new StringBuilder();
for (String part : parts) {
result.append(part);
}
return result.toString();
For the exercise, explain why this avoids intentionally creating a complete immutable prefix at every iteration. Buffer growth and the final conversion still have costs. Capacity selection can reduce resizing, but oversized preallocation wastes memory and arithmetic based on untrusted sizes can overflow.
Our proposed implementation contract states whether null parts are rejected or represented as text, whether separators are inserted, and whether the result must fit an application limit. Optimizing an output that has the wrong delimiter or null policy does not solve the problem.
StringBuilder also provides no synchronization guarantee for shared mutation. A local builder used by one invocation has a different ownership story from a static builder reused by several threads. Do not introduce shared construction state merely to avoid allocations.
How would you make a performance claim credible?
Use the actual JDK version, compiler target, part lengths, character mix, and output contract. OpenJDK's official JMH project provides a benchmark harness; using it does not remove the need to design a fair comparison.
Our proposed comparison varies total output size and the distribution of part lengths. Both implementations must produce the same result, and the benchmark must consume it so unused work is not silently eliminated. Separate input preparation from the construction work being measured.
Include warm-up and multiple independent runs, then inspect allocation as well as time. A one-off stopwatch around a tiny loop can be dominated by startup, compilation, or unrelated work. Report a bounded finding about the tested workload instead of “StringBuilder is always faster.”
Storage claims also need version scope. An OpenJDK engineering presentation describes compact internal string representations. That implementation detail does not change the public UTF-16 code-unit indexing contract into “one byte per Java character.” Avoid calculating heap footprint from character count alone.
Practise semantics and construction as separate skills
These verified questions include direct Java concepts and transferable string-building exercises. They are not a claimed fixed interview sequence. Implement the coding exercises in Java when rehearsing builder ownership, delimiters, and output-size trade-offs.
| Question | What to make explicit |
|---|---|
| Explain Java String Storage and Construction Choices | Separate public semantics from storage and construction choices |
| Explain core Java and Spring fundamentals | Explain immutable String values within broader Java fundamentals |
| Spreadsheet String Concatenation with Dependent Cells | Preserve dependency updates while forming concatenated values |
| Serialize a List of Arbitrary Strings into One String and Back, Then Compare Schemes | Define framing and empty-string behavior before optimizing construction |
| Compress a String by Run-Length Counts, Keeping the Original When Not Shorter | Compare the final output length without repeatedly building prefixes |
For the six-output exercise, explain the expected value, reference identity, and construction rule on separate lines. For the loop exercise, state the explicit copy model, then explain what a runtime measurement would still need to establish.
A strong Java String answer makes semantic claims precise and performance claims falsifiable; it never uses a pooling trick as a substitute for correct value comparison.
Sources and Further Reading
- Java SE 26 JLS: string literals and interning
- Java SE 26 JLS: string concatenation and constant expressions
- Java SE 26 JLS: constant variables
- Java SE 26: String API
- Java SE 26: StringBuilder API
- Java SE 26: Objects equality helpers
- Java SE 26: StringConcatFactory
- OpenJDK: JMH benchmark harness
- OpenJDK: compact strings engineering discussion
References checked October 4, 2026. Expected Java outputs were specification-reviewed; only the original arithmetic model was executed. No JVM timing result is claimed.