Mastering Java requires more than memorizing syntax; it demands the ability to execute code mentally just like the JVM. Below are battle-tested dry run challenges frequently asked in technical coding interviews and university practical exams.
public class Drill1 {
public static void main(String[] args) {
int i = 0;
i = i++ + ++i * 2 + i++;
System.out.println("i = " + i);
}
}
| Sub-Expression | Evaluation Value | Variable 'i' in Local Table | Operand Stack State |
|---|---|---|---|
i++ | 0 | Becomes 1 | Push 0 |
++i | 2 | Becomes 2 | Push 2 |
++i * 2 | 4 | Remains 2 | Multiply: 2 * 2 = 4 |
i++ + (++i * 2) | 4 | Remains 2 | Add: 0 + 4 = 4 |
i++ | 2 | Becomes 3 | Push 2 |
Final Addition | 6 | Overwritten to 6 | 4 + 2 = 6 $\rightarrow$ Assigned to i |
Output: i = 6
public class Drill2 {
public static void main(String[] args) {
String s1 = "Veewom";
String s2 = "Vee" + "wom";
String part = "wom";
String s3 = "Vee" + part;
String s4 = new String("Veewom").intern();
System.out.println(s1 == s2); // Check 1
System.out.println(s1 == s3); // Check 2
System.out.println(s1 == s4); // Check 3
}
}
"Vee" + "wom" is composed of string literals. The compiler performs Constant Folding at compile time into "Veewom", reusing the identical Pool reference.part is a runtime variable, Java constructs a new StringBuilder().append("Vee").append(part).toString(), allocating a fresh object in general Heap memory outside the pool.intern() explicitly fetches the canonical pool reference, matching s1.Console Output:truefalsetrue
public class Drill3 {
public static int testFlow() {
try {
int x = 10 / 2;
return 100;
} catch (Exception e) {
return 200;
} finally {
return 300; // Warning: finally block does not complete normally
}
}
public static void main(String[] args) {
System.out.println("Result: " + testFlow());
}
}
The try block calculates x = 5 and prepares to return 100 by staging it in a return register. Before the stack frame can return, the mandatory finally block executes. Its explicit return 300; discards the staged value of 100 and unconditionally returns 300.
Output: Result: 300
class Parent {
static { System.out.print("1 "); }
{ System.out.print("2 "); }
Parent() { System.out.print("3 "); }
}
class Child extends Parent {
static { System.out.print("4 "); }
{ System.out.print("5 "); }
Child() { System.out.print("6 "); }
}
public class Drill4 {
public static void main(String[] args) {
System.out.print("Start: ");
new Child();
System.out.println();
}
}
142356Output: Start: 1 4 2 3 5 6
Whenever you trace inheritance constructor output, always remember: Static blocks run once per class loading (parent then child). Next, instance initializer and constructor of Parent run, followed by instance initializer and constructor of Child.
Accelerate your Java mastery by studying these tightly connected core tutorials:
Stack vs Heap memory lifecycle.
String Constant Pool mechanics.
Virtual method table lookups.
Comprehensive conceptual questions.