JUNE 2026 Coding interviews test recall. kodr.run tests engineering judgment
JavaIntermediate15 minGeneral

Java OOP Interview Questions (With Code Examples)

Intro

Java OOP questions test something different from algorithm questions. There usually isn't a "brute force vs optimised" axis — instead, the interviewer is checking whether you actually understand why a language feature exists, or whether you've memorised its definition without absorbing the design reasoning behind it. This guide collects the fifteen OOP questions that come up most, each with working code, the printed output, and — for the first five — the common mistake or misconception a stronger answer avoids.

The first five get full treatment: the correct implementation, the broken or naive version most candidates default to, and what the gap between them signals. The remaining ten are tighter: one working example and the one insight that matters.

How interviewers read a Java OOP answer

LayerWhat a weaker answer doesWhat a stronger answer does
Conceptual clarityStates the definition, struggles to give an exampleDefines it and immediately writes a small example unprompted
Design judgmentReaches for inheritance or overriding by defaultNames when composition, an interface, or immutability is the better fit
CorrectnessCode compiles but silently breaks a contract (e.g. equals without hashCode)Respects Java's implicit contracts, not just its syntax
CommunicationRecites the textbook lineTies the concept to a real bug or design decision it prevents


1What is encapsulation in Java, and how do you implement it?

Java · EncapsulationDemo.java
class BankAccount {
    private double balance;


    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }


    public double getBalance() {
        return balance;
    }


    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
}


public class EncapsulationDemo {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);
        account.deposit(500);
        System.out.println(account.getBalance()); // 1500.0
    }
}

Encapsulation is a private field plus public methods that control how it changes — deposit can enforce a rule (amount > 0) that a caller can't bypass. The version most candidates default to skips this and exposes the field directly:


Java · NoEncapsulationDemo.java
class BankAccountUnsafe {
    public double balance;
}


public class NoEncapsulationDemo {
    public static void main(String[] args) {
        BankAccountUnsafe account = new BankAccountUnsafe();
        account.balance = 1000;
        account.balance -= 5000; // no validation — balance goes negative
        System.out.println(account.balance); // -4000.0
    }
}

What this evaluates: design judgment. A public field compiles fine and "works" until an invariant matters — the interviewer is checking whether you reach for private fields and controlled access by default, not just when asked.


2What is the difference between method overloading and method overriding?


Java · OverloadOverrideDemo.java
class Calculator {
    // Overloading: same name, different parameter list, resolved at compile time
    int add(int a, int b) {
        return a + b;
    }


    double add(double a, double b) {
        return a + b;
    }
}


class ScientificCalculator extends Calculator {
    // Overriding: same signature as the parent, resolved at runtime
    @Override
    int add(int a, int b) {
        System.out.println("Adding with logging");
        return super.add(a, b);
    }
}


public class OverloadOverrideDemo {
    public static void main(String[] args) {
        Calculator calc = new ScientificCalculator();
        System.out.println(calc.add(2, 3)); // Adding with logging \n 5
        System.out.println(calc.add(2.5, 3.5)); // 6.0
    }
}

calc is declared as Calculator but holds a ScientificCalculator. add(2, 3) matches the overridden int version and runs the subclass's logging — overriding is resolved by the runtime type. add(2.5, 3.5) only exists on Calculator and was never overridden, so it just runs — overloading was resolved by the compile-time parameter types, before the object's actual type even mattered.

What this evaluates: correctness of terms. Most candidates can define both but conflate when each resolution happens — this is the question that catches that.


3What is the difference between an abstract class and an interface in Java?


Java · AbstractVsInterfaceDemo.java
interface Movable {
    void move(); // implicitly abstract


    default void stop() { // interfaces can carry default behaviour (Java 8+)
        System.out.println("Stopped.");
    }
}


abstract class Vehicle {
    protected String name;


    Vehicle(String name) {
        this.name = name;
    }


    abstract void start(); // subclasses must implement this


    void describe() { // abstract classes can hold shared state + concrete methods
        System.out.println(name + " is a vehicle.");
    }
}


class Car extends Vehicle implements Movable {
    Car(String name) {
        super(name);
    }


    @Override
    void start() {
        System.out.println(name + " engine started.");
    }


    @Override
    public void move() {
        System.out.println(name + " is moving.");
    }
}


public class AbstractVsInterfaceDemo {
    public static void main(String[] args) {
        Car car = new Car("Sedan");
        car.describe(); // Sedan is a vehicle.
        car.start(); // Sedan engine started.
        car.move(); // Sedan is moving.
        car.stop(); // Stopped.
    }
}


Vehicle holds actual state (name) and a finished method (describe) — that's what an abstract class is for: closely related subclasses sharing implementation. Movable is a capability with no state at all, implementable by any unrelated class, and Car can implement several such interfaces but extend only one Vehicle.

What this evaluates: design judgment. A weak answer recites "interfaces have no state, abstract classes can" — a strong one explains when that distinction should drive the choice: shared state and partial implementation among related types → abstract class; a capability across unrelated types → interface.


4What is polymorphism in Java, and how does runtime dispatch work?

Java · PolymorphismDemo.java
class Animal {
    String makeSound() {
        return "Some generic sound";
    }
}


class Dog extends Animal {
    @Override
    String makeSound() {
        return "Bark";
    }
}


class Cat extends Animal {
    @Override
    String makeSound() {
        return "Meow";
    }
}


public class PolymorphismDemo {
    public static void main(String[] args) {
        Animal[] animals = { new Dog(), new Cat(), new Animal() };
        for (Animal a : animals) {
            System.out.println(a.makeSound());
        }
        // Bark
        // Meow
        // Some generic sound
    }
}

Every element in the array is declared as Animal, but each call to makeSound() runs the actual object's version — that's dynamic dispatch. The mistake candidates make is assuming Java always resolves this way, including for overloaded methods:


Java · OverloadResolutionGotcha.java
class Animal {}
class Dog extends Animal {}


class Printer {
    void print(Animal a) {
        System.out.println("Generic animal");
    }


    void print(Dog d) {
        System.out.println("Specific dog");
    }
}


public class OverloadResolutionGotcha {
    public static void main(String[] args) {
        Animal a = new Dog();
        Printer printer = new Printer();
        printer.print(a); // Generic animal — resolved by declared type, not runtime type
    }
}

a holds a Dog at runtime, but print(a) still calls print(Animal), because overload resolution happens at compile time against the declared type. Only overriding is runtime-polymorphic; overloading isn't.

What this evaluates: conceptual clarity. This is the single most common "I thought I understood polymorphism" gap — naming it unprompted is a strong signal.


5How do you correctly override equals() and hashCode() in Java?


Java · EqualsHashCodeDemo.java
import java.util.Objects;


class Point {
    private final int x;
    private final int y;


    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }


    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Point)) return false;
        Point other = (Point) obj;
        return x == other.x && y == other.y;
    }


    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}


public class EqualsHashCodeDemo {
    public static void main(String[] args) {
        Point p1 = new Point(1, 2);
        Point p2 = new Point(1, 2);


        System.out.println(p1.equals(p2)); // true
        System.out.println(p1.hashCode() == p2.hashCode()); // true


        java.util.Set<Point> points = new java.util.HashSet<>();
        points.add(p1);
        points.add(p2);
        System.out.println(points.size()); // 1
    }
}

HashSet relies on the contract that equal objects must have equal hash codes — override both together and it dedupes correctly. Override only equals() and the contract breaks silently:


Java · BrokenEqualsDemo.java
class BrokenPoint {
    private final int x;
    private final int y;


    BrokenPoint(int x, int y) {
        this.x = x;
        this.y = y;
    }


    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof BrokenPoint)) return false;
        BrokenPoint other = (BrokenPoint) obj;
        return x == other.x && y == other.y;
    }
    // hashCode() not overridden — still uses Object's identity-based hash code
}


public class BrokenEqualsDemo {
    public static void main(String[] args) {
        BrokenPoint p1 = new BrokenPoint(1, 2);
        BrokenPoint p2 = new BrokenPoint(1, 2);


        System.out.println(p1.equals(p2)); // true


        java.util.Set<BrokenPoint> points = new java.util.HashSet<>();
        points.add(p1);
        points.add(p2);
        System.out.println(points.size()); // 2 — contract broken
    }
}


p1.equals(p2) still says true, but HashSet hashes them into different buckets using the default identity hash code, so it never even calls equals() to compare them — both end up stored. equals() and hashCode() are a pair; overriding one without the other compiles cleanly and fails silently in exactly the collections most code relies on.

What this evaluates: correctness. This is less about knowing the syntax and more about knowing the contract exists at all — most candidates have been bitten by this in production before they can articulate why.


6What is inheritance, and when should you favour composition over it?


Java · CompositionOverInheritance.java
class Engine {
    void start() {
        System.out.println("Engine starting...");
    }
}


class Car {
    private final Engine engine = new Engine(); // composition: Car "has an" Engine


    void start() {
        engine.start();
        System.out.println("Car ready to drive.");
    }
}


public class CompositionOverInheritance {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
        // Engine starting...
        // Car ready to drive.
    }
}

Evaluates: design judgment — reaching for "has-a" composition instead of "is-a" inheritance when the relationship isn't a genuine subtype, which avoids fragile-base-class problems down the line.


7What is the difference between static and instance members?


Java · StaticVsInstanceDemo.java
class Counter {
    static int totalCount = 0; // shared across every instance
    int instanceId; // unique per instance


    Counter() {
        totalCount++;
        instanceId = totalCount;
    }
}


public class StaticVsInstanceDemo {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();


        System.out.println(c1.instanceId); // 1
        System.out.println(c3.instanceId); // 3
        System.out.println(Counter.totalCount); // 3
    }
}

Evaluates: correctness — knowing a static field belongs to the class and is shared by every instance, while an instance field belongs to each object separately.


8What is constructor chaining (this() and super())?

Java · ConstructorChainingDemo.java
class Person {
    String name;
    int age;


    Person(String name) {
        this(name, 0); // this() call — must be the first statement
        System.out.println("Person(name) called");
    }


    Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person(name, age) called");
    }
}


class Employee extends Person {
    Employee(String name) {
        super(name); // super() call — must be the first statement
        System.out.println("Employee(name) called");
    }
}


public class ConstructorChainingDemo {
    public static void main(String[] args) {
        new Employee("Asha");
        // Person(name, age) called
        // Person(name) called
        // Employee(name) called
    }
}

Evaluates: correctness — knowing this()/super() must be the first statement in a constructor, and being able to trace the resulting call order rather than guessing at it.


9What does the final keyword actually mean in Java?

Java · FinalKeywordDemo.java
final class ImmutableConfig { // final class: cannot be subclassed
    final int maxRetries; // final field: assigned exactly once


    ImmutableConfig(int maxRetries) {
        this.maxRetries = maxRetries;
    }


    final void logConfig() { // final method: cannot be overridden
        System.out.println("Max retries: " + maxRetries);
    }
}


public class FinalKeywordDemo {
    public static void main(String[] args) {
        ImmutableConfig config = new ImmutableConfig(3);
        config.logConfig(); // Max retries: 3
    }
}

Evaluates: conceptual clarity — final means three different things depending on where it's applied (class, method, or variable), and a strong answer names all three without prompting.


10What are the access modifiers in Java, and how do they actually differ?


Java · AccessModifiersDemo.java
public class AccessModifiersDemo {
    public int publicField = 1; // visible from any class, any package
    protected int protectedField = 2; // visible in this package, plus subclasses elsewhere
    int defaultField = 3; // visible only within this package (no modifier)
    private int privateField = 4; // visible only inside this class


    public static void main(String[] args) {
        AccessModifiersDemo demo = new AccessModifiersDemo();
        System.out.println(demo.publicField); // 1
        System.out.println(demo.protectedField); // 2
        System.out.println(demo.defaultField); // 3
        System.out.println(demo.privateField); // 4
    }
}

Evaluates: conceptual clarity — the real scope of each level (especially default/package-private and protected, which candidates routinely blur) rather than just "public is more open than private."


11What is the difference between a default method and an abstract method in an interface?


Java · DefaultMethodDemo.java
interface Notifier {
    void send(String message); // abstract — every implementer must provide a body


    default void sendUrgent(String message) { // default — has a body, overriding is optional
        send("[URGENT] " + message);
    }
}


class EmailNotifier implements Notifier {
    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}


public class DefaultMethodDemo {
    public static void main(String[] args) {
        Notifier notifier = new EmailNotifier();
        notifier.send("Server is up"); // Email: Server is up
        notifier.sendUrgent("Server is down"); // Email: [URGENT] Server is down
    }
}

Evaluates: conceptual clarity — default methods (Java 8+) let an interface evolve without breaking every class that already implements it, which is the actual reason they exist, not just "an interface method with a body."


12How do you implement immutability in a Java class?


Java · ImmutablePointDemo.java
final class ImmutablePoint {
    private final int x;
    private final int y;


    ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }


    int getX() { return x; }
    int getY() { return y; }


    ImmutablePoint translate(int dx, int dy) {
        return new ImmutablePoint(x + dx, y + dy); // returns a new instance instead of mutating
    }
}


public class ImmutablePointDemo {
    public static void main(String[] args) {
        ImmutablePoint p1 = new ImmutablePoint(1, 1);
        ImmutablePoint p2 = p1.translate(2, 3);


        System.out.println(p1.getX() + ", " + p1.getY()); // 1, 1
        System.out.println(p2.getX() + ", " + p2.getY()); // 3, 4
    }
}

Evaluates: design judgment — final class, final fields, no setters, and returning a new instance instead of mutating state, which together are what actually make a class immutable and safe to share across threads.


13What is the diamond problem, and how does Java avoid it?


Java · DiamondProblemDemo.java
interface Vehicle {
    default String horn() {
        return "Generic horn";
    }
}


interface Boat {
    default String horn() {
        return "Boat horn";
    }
}


// Implementing both with conflicting default methods forces an explicit resolution
class AmphibiousCar implements Vehicle, Boat {
    @Override
    public String horn() {
        return Vehicle.super.horn() + " + " + Boat.super.horn();
    }
}


public class DiamondProblemDemo {
    public static void main(String[] args) {
        AmphibiousCar car = new AmphibiousCar();
        System.out.println(car.horn()); // Generic horn + Boat horn
    }
}

Evaluates: conceptual clarity — Java has no multiple inheritance of state, but two interfaces can still both offer a conflicting default method; the compiler refuses to silently pick one and forces AmphibiousCar to override horn() and resolve it explicitly, as shown with Vehicle.super.horn().


14What is the difference between this and super?


Java · ThisVsSuperDemo.java
class Base {
    String label = "Base label";


    void show() {
        System.out.println("Base show()");
    }
}


class Derived extends Base {
    String label = "Derived label";


    void show() {
        System.out.println(this.label); // this -> current object's own field
        System.out.println(super.label); // super -> parent's field, even though shadowed
        super.show(); // explicitly calls Base's version
    }
}


public class ThisVsSuperDemo {
    public static void main(String[] args) {
        new Derived().show();
        // Derived label
        // Base label
        // Base show()
    }
}

Evaluates: conceptual clarity — fields are shadowed and resolved by reference type, not overridden and resolved by runtime type like methods are; super is what lets you deliberately reach past the shadow to the parent's version.


15What is the difference between abstraction and encapsulation?


Java · AbstractionVsEncapsulationDemo.java
// Abstraction: hides *what* is complex behind a simple contract
interface PaymentGateway {
    boolean processPayment(double amount);
}


// Encapsulation: hides *how* internal state is protected and manipulated
class StripeGateway implements PaymentGateway {
    private double dailyLimit = 5000;
    private double processedToday = 0;


    @Override
    public boolean processPayment(double amount) {
        if (processedToday + amount > dailyLimit) {
            return false; // internal rule enforced — the caller never sees processedToday
        }
        processedToday += amount;
        return true;
    }
}


public class AbstractionVsEncapsulationDemo {
    public static void main(String[] args) {
        PaymentGateway gateway = new StripeGateway(); // caller only knows the interface
        System.out.println(gateway.processPayment(3000)); // true
        System.out.println(gateway.processPayment(3000)); // false — limit enforced internally
    }
}

Evaluates: conceptual clarity — the most-confused pair on this list. Abstraction is about the interface hiding complexity from the caller; encapsulation is about the class protecting and controlling its own internal state. They usually show up together, but they answer different questions, and a strong answer separates them without hedging.



How should you prepare for a Java OOP interview?

  1. Write the example before you say the definition. If you can't produce a two-class demo for a concept in under a minute, you don't own it yet — you've memorised it.
  2. Actively hunt the confusion pairs. Overloading vs overriding, abstraction vs encapsulation, interface vs abstract class — practise explaining each pair in one sentence apiece, not as a shared paragraph.
  3. Say the contract out loud. equals/hashCode, this()/super() ordering, final's three meanings — these are rules the compiler won't always catch for you, so naming them unprompted is what separates a pass from an offer.

Frequently Asked Questions

What are the most commonly asked Java OOP interview questions?
Four groups come up repeatedly: the core pillars (encapsulation, inheritance, polymorphism, abstraction), class mechanics (static vs instance, constructors, final), interfaces vs abstract classes, and object contracts (equals/hashCode, immutability). The fifteen here cover the patterns behind nearly all of them.
What is the most commonly confused pair of OOP concepts in interviews?
Abstraction and encapsulation. Both are described as "hiding something," which makes them sound interchangeable - the useful distinction is what each one hides: abstraction hides complexity from the caller, encapsulation hides state inside the class.
Do I need to memorise design patterns for a Java OOP interview?
Not for these questions specifically - they test whether you understand the language's own mechanisms (interfaces, overriding, immutability) well enough to reason about design trade-offs, not whether you can name a pattern from a book.
Is overloading resolved at compile time or runtime?
Compile time, based on the declared type of the reference - this is the exact gap Q4's OverloadResolutionGotcha example is built to expose.
How long does it take to prepare for a Java OOP interview?
Less about time and more about repetition on the confusion pairs — most candidates already know each concept individually; the gap is being able to contrast two of them cleanly, on the spot, without hedging.