Round 1: numbers and arithmetic
What does System.out.println(0.1 + 0.2) print?
0.30000000000000004. Neither 0.1 nor 0.2 has an exact binary representation, so the sum is very slightly above 0.3 and Java prints enough digits to distinguish it from the nearest double to 0.3. Use BigDecimal for money.
What does System.out.println(Integer.MAX_VALUE + 1) print?
−2147483648, which is Integer.MIN_VALUE. Integer arithmetic wraps around silently on overflow rather than throwing - one of the more dangerous defaults in the language. Math.addExact throws instead, if you want to know.
byte b = 10; b = b + 1; - does this compile?
No. b + 1 promotes to int, and assigning an int to a byte needs an explicit cast. But b += 1 does compile, because compound assignment operators include an implicit narrowing cast - the single most surprising asymmetry in Java's type rules.
What does System.out.println(Math.min(Double.MIN_VALUE, 0.0d)) print?
0.0. Double.MIN_VALUE is not the most negative double - it is the smallest positive non-zero value, roughly 4.9 × 10⁻³²⁴. The most negative is −Double.MAX_VALUE. Integer.MIN_VALUE, confusingly, does mean the most negative int.
What do 1/0.0, 0.0/0.0 and (Double.NaN == Double.NaN) evaluate to?
Infinity, NaN, and false. Floating-point division by zero does not throw - only integer division does. And NaN is not equal to itself by the IEEE specification, which is why you must use Double.isNaN() to test for it.
int i = 5; i = i++; What is i?
5. The postfix increment returns the old value, which is then assigned back over the incremented one. The increment happens and is immediately overwritten - the assignment wins.
Practice this now
Related reading
Round 2: characters and strings
What does System.out.println('a' + 1) print, and what does System.out.println("" + 'a' + 1) print?
98 and a1. In the first, char is promoted to int and arithmetic occurs. In the second, the leading empty String forces concatenation from the left, so the char and the int are both appended as text. Same operands, entirely different mechanism.
char c = 'a'; c += 1; System.out.println(c); - what prints?
b. Compound assignment's implicit cast applies here too, narrowing the int result back to char. Writing c = c + 1 would not compile without a cast, for exactly the reason it fails with byte.
String a = "hello"; String b = "hel" + "lo"; What does a == b return?
true. "hel" + "lo" is a compile-time constant expression, so the compiler folds it into the single literal "hello", which is interned in the string pool. If either part were a non-final variable, the concatenation would happen at runtime and == would be false.
String s = "hello"; s.replace('l', 'L'); System.out.println(s); - what prints?
hello, unchanged. Strings are immutable, so replace returns a new String and discards it if you do not assign the result. Every String method that appears to modify actually returns a new object.
What happens with switch (s) when s is a null String?
It throws a NullPointerException. A String switch internally calls hashCode on the value, so null fails before any case - including a default case, which does not protect you. Check for null before the switch.
Practice this now
Related reading
Round 3: control flow and exceptions
What does this return? try { return 1; } finally { return 2; }
2. A return in finally replaces the value the try block was returning, silently discarding it. It also swallows any exception the try block was about to throw. This is why returning from finally is considered a bug rather than a technique.
int x = 1; try { return x; } finally { x = 2; } - what is returned?
1. The return value is evaluated and held before finally runs, so mutating the variable afterwards has no effect on what was already captured. Contrast this with returning from finally, which does change the result.
Does the finally block always execute?
Almost always - it runs after return, after break, and while an exception propagates. The genuine exceptions are System.exit(), an abnormal JVM termination such as a crash or kill, and a thread being permanently blocked or the JVM running out of resources.
What is the difference between & and && for boolean operands?
&& short-circuits: if the left side is false the right side is never evaluated. & always evaluates both. This matters when the right side has side effects or would throw - which is why if (p != null && p.isValid()) is safe and the & version is not.
Practice this now
Round 4: collections and generics
List<Integer> list = new ArrayList<>(List.of(10, 20, 30)); list.remove(1); What is left?
[10, 30]. remove(int index) and remove(Object) are overloads, and the literal 1 is an int, so the index overload wins at compile time - removing the element at position 1. To remove the value 1 you must write remove(Integer.valueOf(1)). This is Java's most notorious overload trap.
What happens if you call add() on a list returned by Arrays.asList()?
It throws UnsupportedOperationException. Arrays.asList returns a fixed-size view backed by the array - set() works, but add() and remove() do not. List.of() goes further and returns a fully immutable list where set() fails too.
What happens if you remove an element from an ArrayList while iterating it with a for-each loop?
It throws ConcurrentModificationException on the next iteration, because the iterator detects the structural modification. Use iterator.remove(), removeIf(), or iterate over a copy. Curiously, removing the second-to-last element can slip through without throwing, which makes the bug intermittent.
You add an object to a HashSet, then mutate a field used by its hashCode. What happens?
The object becomes effectively unreachable. The set looks in the bucket implied by the new hash code, where the object is not stored, so contains() returns false and the element cannot be removed - yet it still occupies space and appears during iteration. Never mutate the fields that define a hash-based key.
Why can you write List<String> l = new ArrayList<>() but not List<Object> lo = someListOfString?
Because generics are invariant. A List<String> is not a List<Object>, even though String is an Object, since allowing it would let you insert an Integer into a list of Strings. Use List<? extends Object> when you only need to read.
Practice this now
Round 5: objects, initialisation and autoboxing
Integer x = true ? null : 0; - what happens at runtime?
It throws a NullPointerException. Because one branch of the ternary is the primitive int 0, the whole expression's type is int, so the null branch must be unboxed - and unboxing null throws. Change 0 to Integer.valueOf(0) and it works.
In what order do static blocks, instance initialisers and constructors run?
Static initialisers and static blocks run once when the class is first loaded, in source order. Then for each object: the superclass constructor, followed by instance field initialisers and instance blocks in source order, and finally the constructor body. That last ordering catches people out - the constructor body runs after field initialisers, not before.
A field is declared in both a superclass and a subclass. Which one does a superclass-typed reference read?
The superclass field. Fields are resolved statically from the declared type of the reference, while methods are dispatched dynamically on the object's actual type. This asymmetry is why shadowing fields is a bad idea.
You override equals() but not hashCode(). What breaks?
Every hash-based collection. Two objects that are equal may return different hash codes, so a HashMap stores and looks for them in different buckets - meaning get() fails to find a key you inserted, and a HashSet can hold two 'equal' elements. The contract is that equal objects must have equal hash codes.
What does new ArrayList<String>() {{ add("a"); }} actually create?
An anonymous subclass of ArrayList with an instance initialiser block - the 'double brace' idiom. It works, but it creates an extra class, holds a hidden reference to the enclosing instance, and can break serialisation. Use List.of() or a builder instead.
Practice this now
How did you do?
Twenty questions, and almost none of them are about knowing an API. They are about the places where Java's rules diverge from intuition: implicit casts inside compound assignment, compile-time overload resolution on declared types, unboxing in mixed-type expressions, and the difference between what fields and methods do under inheritance. Those are the mechanisms worth taking away, more than the individual answers.
A realistic reading: getting most of round 1 is a good sign of solid fundamentals; getting the remove(int) question, the ternary NullPointerException and the finally-with-return question right suggests you have been bitten by them in real code. Nobody should feel bad about missing several - these are the questions that cause production bugs precisely because they are counter-intuitive.
- ✓Write down which mechanism caught you, not which question. There are about six mechanisms here and twenty questions built from them.
- ✓Try each surprising snippet in a real file. Seeing the compiler refuse b = b + 1 while accepting b += 1 fixes it permanently.
- ✓Then drill Java MCQs under time pressure, because recognising these patterns quickly is what interviews actually reward.
Practice this now
