Java Language & Platform — Interview Q&A Matrix
Parent stack: Core Java Runtime Technology: Java Language & Platform Levels: Junior · Architect Questions: 4
Each entry contains a Question, the Expected Depth, an Ideal Answer, and Red Flags.
Junior
Q1 — == vs .equals() and the Integer cache
Question: What is the difference between == and .equals() for objects, and why does the following print true then false?
Integer a = 100, b = 100;
Integer c = 1000, d = 1000;
System.out.println(a == b); // ?
System.out.println(c == d); // ?Expected Depth:
- Explains that
==compares references and.equals()compares values. - Explains the
Integercache for values from-128to127. - Knows that wrapper types and
Stringmust be compared with.equals(). - Uses
Objects.equals()for a null-safe comparison. - Understands the
equals()andhashCode()contract.
Ideal Answer:
==checks whether two variables point to the same object..equals()checks whether two objects have the same value, based on the class definition.- Autoboxing calls
Integer.valueOf(), which caches objects for values from-128to127. Soaandbpoint to the same cached object, anda == bistrue. 1000is outside the cache range, socanddare two different objects.c == disfalse, butc.equals(d)istrue.- Always compare values of wrapper types and
Stringwith.equals():
System.out.println(c.equals(d)); // true, compares value- Use
Objects.equals()when a value may benull. It avoids aNullPointerException:
Objects.equals(null, 1000); // false, no exception- If a class overrides
equals(), it must also overridehashCode(). Two equal objects must return the same hash code, or hash-based collections such asHashMapandHashSetwill not work correctly.
Red Flags:
- Says that
==always works for numbers or objects. - Cannot explain why the second line prints
false. - Overrides
equals()but nothashCode(). - Compares wrapper objects with
==in business logic.
Q2 — Checked vs unchecked exceptions & finally
Question: What is the difference between checked and unchecked exceptions? What does finally guarantee, and what happens if you return inside it?
Expected Depth:
- Distinguishes checked exceptions (
Exception) from unchecked exceptions (RuntimeException,Error). - Knows that the compiler forces the caller to handle or declare checked exceptions.
- Knows that
finallyalways runs, even after areturnor an exception. - Understands that a
returnorthrowinsidefinallyhides earlier results. - Prefers try-with-resources to close resources.
Ideal Answer:
- Checked exceptions extend
Exceptionbut notRuntimeException. The caller must catch them or declare them withthrows, and the compiler enforces this. Example:IOException. - Unchecked exceptions extend
RuntimeExceptionorError. The compiler does not force handling. Examples:NullPointerException,IllegalArgumentException. finallyruns after thetryblock, whether it ends normally, with areturn, or with an exception. Use it for cleanup.- A
returnorthrowinsidefinallyreplaces any earlierreturnor exception. This hides errors, so avoid it:
int value() {
try {
return 1;
} finally {
return 2; // always returns 2 and hides the first result
}
}- Prefer try-with-resources to close resources automatically:
try (var reader = Files.newBufferedReader(path)) {
return reader.readLine();
} // reader.close() runs automatically, even on an exception- Try-with-resources also keeps the original exception. If
close()fails too, that second error is attached as a suppressed exception instead of replacing the first one.
Red Flags:
- Says that all exceptions must be caught.
- Thinks that
finallydoes not run when an exception is thrown. - Uses
returnorthrowinsidefinally. - Closes resources by hand in
finallyinstead of using try-with-resources.
Q3 — String immutability, pool, and StringBuilder
Question: Why is String immutable in Java? What is the string pool, and when should you use StringBuilder?
Expected Depth:
- Explains how immutability gives thread safety, safe caching, and security.
- Knows the string pool and
intern(). - Knows that
+inside a loop creates many temporary objects. - Uses
StringBuilderfor loops and knows thatStringBufferis the synchronized version. - Knows that a single
+expression is already optimized by the compiler.
Ideal Answer:
Stringis immutable, so its value never changes after creation. This makes it safe to share between threads without locks, safe to cache (for example as aHashMapkey with a cached hash code), and safe for sensitive values such as file paths and class names.- String literals are stored in the string pool, so equal literals share one object.
intern()returns the pooled instance, but use it with care because the pool can grow large. - A
+inside a loop creates a newStringon each step, which wastes memory and time. UseStringBuilder:
var builder = new StringBuilder();
for (String part : parts) {
builder.append(part);
}
String result = builder.toString();- A single expression such as
a + b + cis fine. The compiler already turns it into efficient code, so you do not needStringBuilderthere. - Use
StringBuilderin single-thread code. UseStringBufferonly when several threads share the same buffer, because its synchronization has a cost. - For long fixed text, a text block improves readability (Java 15+):
String json = """
{ "status": "OK" }
""";Red Flags:
- Says that
Stringis mutable. - Uses
+=inside a large loop and does not know the cost. - Confuses
StringBuilder(not thread-safe) withStringBuffer(synchronized). - Calls
intern()everywhere without a clear reason.
Architect
Q4 — Designing for backward/forward compatibility (records, sealed, modules)
Question: You're designing a shared domain library used by 40 services. How do modern Java features (records, sealed types, JPMS) shape an API that must evolve without breaking consumers?
Expected Depth:
- Explains records as immutable data carriers and their limits.
- Uses sealed types with pattern matching for controlled change.
- Uses modules or strict package boundaries to hide internal classes.
- Applies semantic versioning and a deprecation plan.
- Keeps the serialized or wire format stable and separate from internal types.
Ideal Answer:
- Use records for immutable DTOs and value objects. A record provides correct
equals(),hashCode(), andtoString()with little code:
public record Money(String currency, long amountMinor) {}- Adding a component changes the canonical constructor, so plan changes carefully. Keep the wire format stable with an explicit mapping instead of serializing the record directly.
- Use sealed interfaces for a fixed set of subtypes. With a pattern-matching
switch, the compiler checks that every case is handled. When you add a subtype, each incompleteswitchfails to compile, so you can find every place that needs an update:
public sealed interface PaymentEvent
permits Authorized, Captured, Refunded {}
String describe(PaymentEvent event) {
return switch (event) {
case Authorized a -> "auth " + a.amount();
case Captured c -> "capture " + c.amount();
case Refunded r -> "refund " + r.amount();
}; // the compiler checks that all types are covered
}- Use the module system (JPMS), or at least strict package rules, to export only stable API packages and hide internal classes. This stops consumers from depending on private details.
- Follow semantic versioning: additive changes are minor, and breaking changes are major. Deprecate an API first, then remove it after a clear period.
Red Flags:
- Treats records as normal mutable classes and ignores compatibility.
- Has no versioning or deprecation plan.
- Exports internal packages to all consumers.
- Exposes internal types directly as the public wire format.
Scoring Rubric
| Level | Pass bar |
|---|---|
| Junior | Explains basic language features and uses standard APIs correctly. |
| Senior | Explains trade-offs, finds risks, and writes correct code. |
| Architect | Defines clear platform guidance and designs APIs that evolve safely. |