Java Practice Drills: Tricky Code Tracing & Memory Dry Runs

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.

Drill 1: The Post-Increment Assignment Trap

public class Drill1 {
    public static void main(String[] args) {
        int i = 0;
        i = i++ + ++i * 2 + i++;
        System.out.println("i = " + i);
    }
}
Step-by-Step JVM Operand Stack Trace
Sub-ExpressionEvaluation ValueVariable 'i' in Local TableOperand Stack State
i++0Becomes 1Push 0
++i2Becomes 2Push 2
++i * 24Remains 2Multiply: 2 * 2 = 4
i++ + (++i * 2)4Remains 2Add: 0 + 4 = 4
i++2Becomes 3Push 2
Final Addition6Overwritten to 64 + 2 = 6 $\rightarrow$ Assigned to i

Output: i = 6

Drill 2: String Constant Pool vs Heap Object Trap

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
    }
}
Dry Run Trace: Compiler Constant Folding & intern()
  • Check 1 (s1 == s2) $\rightarrow$ TRUE: "Vee" + "wom" is composed of string literals. The compiler performs Constant Folding at compile time into "Veewom", reusing the identical Pool reference.
  • Check 2 (s1 == s3) $\rightarrow$ FALSE: Because 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.
  • Check 3 (s1 == s4) $\rightarrow$ TRUE: intern() explicitly fetches the canonical pool reference, matching s1.

Console Output:
true
false
true

Drill 3: Return Value in Finally Block Override

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());
    }
}
Dry Run Trace: JVM Exception Dispatcher

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

Drill 4: Static Initialization & Inheritance Order

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();
    }
}
Dry Run Trace: Class Loading & Instance Initialization
  1. Parent static block executes on class load: 1
  2. Child static block executes on class load: 4
  3. Parent instance block executes: 2
  4. Parent constructor executes: 3
  5. Child instance block executes: 5
  6. Child constructor executes: 6

Output: Start: 1 4 2 3 5 6

BCA / University Practical Exam Tip

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.

Frequently Asked Questions (FAQ)

Q1. Why are dry runs and memory tracing essential for Java students and interviewees?
Dry runs force you to mentally simulate the JVM Execution Engine, Stack Frame allocation, and Heap references. Top tech interviews (Google, Amazon) and university practical exams routinely test edge cases like operator precedence, static initialization order, string pool caching, and finally block execution order.
Q2. What happens when a finally block contains a return statement?
If a finally block executes a return statement, it overrides and suppresses any previous return statement or thrown exception from the try or catch block. This is considered an anti-pattern in production code.
Q3. Why does Integer a = 127; Integer b = 127; return true for a == b, but false for 128?
Java maintains an internal Integer Cache for boxed values ranging from -128 to 127. Values in this range return the exact same cached reference object on the heap, making identity comparison (==) true. For values >= 128, distinct new objects are instantiated on the heap, making == false.

Explore Related Java Tutorials

Accelerate your Java mastery by studying these tightly connected core tutorials:

JVM Memory Architecture →

Stack vs Heap memory lifecycle.

Java Strings & Memory →

String Constant Pool mechanics.

Polymorphism & Dispatch →

Virtual method table lookups.

120+ Interview Q&A →

Comprehensive conceptual questions.

Share Share on Facebook Share on Twitter Share on LinkedIn Pin on Pinterest Share on Stumbleupon Share on Tumblr Share on Reddit Share on Diggit

You may also like this!