The "what does this print?" round for senior engineers — string concatenation order, numeric promotion, pre/post increment, string identity, division by zero, floating-point comparison, switch on null, variable scope — and OOP edge cases — private and static method "overriding", abstract + final, constructors with return types, constructor inheritance, return-type-only overloads, this() with super(), object slicing, and casting vs dynamic dispatch.
Published September 25, 2026
Senior interviews often open with rapid output-prediction questions. They aren't hard, but they test whether you reason from the language rules, rather than guessing. For each one:
Naming the rule is what separates an 8-year answer from a lucky one.
System.out.println(10 + 20 + "Java" + 10 + 20); print?Short answer: 30Java1020.
Key points to cover:
+ is left-associative, and evaluated left to right.10 + 20 are both int, so this is numeric addition, giving 30.30 + "Java" has a String operand, so it becomes string concatenation: "30Java".+ concatenates: "30Java" + 10 gives "30Java10", then "30Java1020".... + (10 + 20) prints 30Java30.Learn it in depth → String Manipulation
byte b = 10; b = b * 2; fail to compile?Short answer: Because of binary numeric promotion. Arithmetic on byte, short and char operands is done in int, so b * 2 is an int. Assigning an int expression to a byte is a narrowing conversion, and needs an explicit cast.
byte b = 10;
b = (byte) (b * 2); // explicit narrowing
b *= 2; // compiles: compound assignment includes an implicit cast, (byte)(b * 2)
byte c = 10 * 2; // compiles: constant expression that fits in a byte
Common trap: b *= 2 compiles, but overflows silently. byte x = 100; x *= 2; gives -56.
int a = 5; System.out.println(a++ + ++a); print?Short answer: 12, and a ends up as 7.
Key points to cover:
a++ yields the old value, 5, then sets a to 6.++a increments first, so a becomes 7, and it yields 7.5 + 7 = 12.String s1 = "abc"; String s2 = new String("abc"); System.out.println(s1 == s2); print?Short answer: false.
"abc" is a literal, interned in the string pool.new String("abc") always creates a new heap object.== compares references, so they differ.s1.equals(s2) is true, and s1 == s2.intern() is true.Short answer:
5 / 0, 5 % 0) throws ArithmeticException: / by zero.5.0 / 0 gives Infinity;-5.0 / 0 gives -Infinity;0.0 / 0 gives NaN;5 % 0.0 gives NaN.Key points to cover:
double first, so 5 / 0.0 gives Infinity, with no exception.NaN != NaN is true. Use Double.isNaN(x).Math.floorDiv/floorMod also throw for a zero int divisor.System.out.println("Result: " + 2 + 3 * 4); print?Short answer: Result: 212.
* has higher precedence than +, so 3 * 4 = 12 is computed first."Result: " + 2 gives "Result: 2", then + 12 gives "Result: 212".To print Result: 14, write "Result: " + (2 + 3 * 4).
Short answer: No. A local variable's scope is the block where it's declared: the loop body, or the for header for the loop variable. Referencing it after the loop is a compile error ("cannot find symbol"). Declare it before the loop if you need the value afterwards.
int lastIndex = -1;
for (int i = 0; i < items.size(); i++) {
String item = items.get(i); // scope: this iteration's body
if (item.isBlank()) lastIndex = i;
}
// item and i are not accessible here; lastIndex is
Key points to cover:
for loop variable i can't be.==?Short answer: Most decimal fractions can't be represented exactly in binary, so arithmetic accumulates rounding error: 0.1 + 0.2 == 0.3 is false (the sum is 0.30000000000000004). There are also special cases:
NaN == NaN is false.0.0 == -0.0 is true, but Double.valueOf(0.0).equals(-0.0) is false.What to do instead:
Math.abs(a - b) <= 1e-9, or relative to magnitude, or a few Math.ulp units.BigDecimal for money, compared with compareTo. equals also compares the scale, so 2.0 isn't equal to 2.00.Double.compare for sorting.static boolean nearlyEqual(double a, double b, double relTol) {
return Math.abs(a - b) <= relTol * Math.max(Math.abs(a), Math.abs(b));
}
new BigDecimal("0.1").add(new BigDecimal("0.2")).compareTo(new BigDecimal("0.3")) == 0; // true
Common trap: new BigDecimal(0.1) captures the inexact binary value. Use BigDecimal.valueOf(0.1), or the String constructor.
switch receives null?Short answer: A classic switch on a String, an enum or a boxed type throws NullPointerException when the selector is null. It calls hashCode(), ordinal() or unboxes the value, and the default branch doesn't catch it.
Since Java 21, pattern-matching switch can handle it explicitly, with case null:
String label = switch (status) { // status may be null
case null -> "unknown";
case "A", "ACTIVE" -> "active";
default -> "other";
};
Before Java 21, guard with if (x == null), or Objects.requireNonNullElse(x, "").
Short answer: No. Private methods aren't inherited, so a subclass method with the same signature is a completely separate method. Calls to it from inside the superclass always run the superclass's private version. There's no dynamic dispatch, because private methods are bound at compile time (invokespecial, or a nestmate invokevirtual that still can't be overridden).
class Base {
private String id() { return "base"; }
String describe() { return id(); } // always calls Base.id()
}
class Child extends Base {
String id() { return "child"; } // a new method; adding @Override here is a compile error
}
new Child().describe(); // "base"
Key points to cover:
@Override catches this mistake at compile time. Always use it.class A { int m() { return 1; } } class B extends A { int m() { return 2; } } A obj = new B(); System.out.println(obj.m());Short answer: 2. Instance methods use dynamic dispatch: the JVM chooses the implementation from the runtime class of the object (B), not from the declared reference type (A). The reference type only decides which methods are visible at compile time.
Learn it in depth → Inheritance and Polymorphism
abstract and final?Short answer: No, it's a compile error: "illegal combination of modifiers". abstract means "must be subclassed to be used", and final means "can't be subclassed". The same goes for an abstract final method.
Key points to cover:
sealed abstract class Shape permits Circle, Square.final class with a private constructor.Short answer: Then it isn't a constructor. It's an ordinary method that happens to have the class's name. It compiles (IDEs warn you), but new MyClass() uses the default constructor, so your "initialisation" code never runs. That's a classic silent bug.
class Account {
int balance;
void Account() { balance = 100; } // a method, not a constructor!
}
new Account().balance; // 0
Short answer: No. Constructors are not members, so they aren't inherited, and they can't be overridden. A subclass must declare its own constructors, and each one must call a superclass constructor:
super(args);super(), which fails to compile if the superclass has no accessible no-arg constructor.Key points to cover:
@RequiredArgsConstructor, @SuperBuilder) generate constructors. They don't inherit them either.Short answer: No, they're hidden, not overridden. A static method with the same signature in a subclass hides the parent's method. The call is resolved at compile time, from the reference type, so there's no polymorphism.
class Parent { static String who() { return "parent"; } }
class Child extends Parent { static String who() { return "child"; } }
Parent p = new Child();
p.who(); // "parent": uses the static type (and IDEs warn: call it as Parent.who())
Child.who(); // "child"
Key points to cover:
@Override on a static method is a compile error.Short answer: It's a compile error: "method already defined". Java identifies a method by its name + parameter types (its signature). The return type isn't part of it, so the call m() would be ambiguous.
Key points to cover:
Animal create() can become Dog create() in a subclass.void f(List<String>) and void f(List<Integer>).super() and this() both be used in the same constructor?Short answer: No. An explicit constructor invocation, this(...) or super(...), must be the first statement, so there's room for only one. Constructor chaining solves this: this(...) delegates to another constructor, which eventually calls super(...).
class Order {
Order(String id) { this(id, Instant.now()); } // delegates
Order(String id, Instant at) { super(); /* ... */ } // the chain ends in super()
}
Key points to cover:
this, such as argument validation, may appear before the super(...) or this(...) call. But there can still be only one explicit constructor call.Short answer: Object slicing is a C++ problem. Copying a derived object by value into a base-class variable copies only the base part, and the derived fields and overrides are "sliced off". Java doesn't slice: object variables hold references, so Base b = derived; copies the reference, and the object stays a complete Derived, with its overrides still active.
Key points to cover:
@JsonTypeInfo): the subclass fields disappear on deserialisation.Short answer: The overriding method of the actual object still runs. Casting a reference never changes the object, only the compile-time view. ((A) new B()).m() calls B.m(). What does depend on the static (cast) type:
class A { String name = "A"; String m() { return "A.m"; } }
class B extends A { String name = "B"; String m() { return "B.m"; } }
B b = new B();
((A) b).m(); // "B.m": dynamic dispatch
((A) b).name; // "A": field access uses the static type
Key points to cover:
ClassCastException at runtime. Use instanceof pattern matching: if (a instanceof B bb).Q: What does char c = 'A'; c += 1; System.out.println(c); print, and why does c = c + 1 fail?
A: It prints B. c += 1 includes an implicit narrowing cast back to char. c = c + 1 produces an int, which needs an explicit (char) cast.
Q: What does Integer a = 127, b = 127; a == b give? And with 128?
A: true for 127, because autoboxing uses Integer.valueOf, which caches −128..127. With 128 it's normally false: two different objects. Always compare boxed numbers with equals, or unbox first.
Q: What does Math.abs(Integer.MIN_VALUE) return?
A: Integer.MIN_VALUE, which is still negative, because +2147483648 doesn't fit in an int. Use Math.absExact, which throws on overflow, or widen to long.
Q: What does System.out.println(1.0 / 0 == Double.POSITIVE_INFINITY); print?
A: true. Floating-point division by zero gives infinity, and infinities compare equal to themselves (unlike NaN).