Test your Object-Oriented Programming knowledge with 20 Java interview questions covering inheritance, polymorphism, encapsulation, abstraction, SOLID principles, design patterns, and composition vs inheritance.
Below are all 20 questions covered in this quiz, grouped by topic. Each question includes the correct answer and a detailed explanation to help you prepare for your next interview.
What is encapsulation in Java?
Bundling data (fields) and methods that operate on that data into a single unit, and restricting direct access to the internal state
Encapsulation is one of the four pillars of OOP. It means wrapping data (fields) and the code that manipulates it (methods) together in a class, while hiding the internal state from outside access using access modifiers like private. Getters and setters provide controlled access. This protects the object's integrity by preventing external code from putting the object into an invalid state.
Which access modifier makes a field visible only within the same package and subclasses?
protected
The protected access modifier allows access from within the same package (like default access) and additionally from subclasses even if they are in a different package. private restricts access to the declaring class only, public allows access from everywhere, and default (package-private) restricts access to the same package only. In interviews, candidates often confuse protected with default access — the key difference is that protected grants access to subclasses across packages.
What is the output of the following code?
class Animal {
void sound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Bark");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.sound();
}
}Bark
This demonstrates runtime polymorphism (dynamic method dispatch). Even though the reference type is Animal, the actual object is a Dog. At runtime, the JVM looks at the actual object type to determine which overridden method to call. Since Dog overrides sound(), the Dog version is invoked, printing "Bark". This is a fundamental concept — the method that gets called depends on the object type, not the reference type.
What is the difference between method overloading and method overriding?
Overloading is resolved at compile time with same method name but different parameters; overriding is resolved at runtime with the same method signature in a subclass
Method overloading (compile-time polymorphism) occurs when multiple methods in the same class share the same name but differ in parameter types, number, or order. The compiler determines which method to call based on the arguments. Method overriding (runtime polymorphism) occurs when a subclass provides its own implementation of a method already defined in its superclass, with the exact same signature. The JVM determines which method to call at runtime based on the actual object type.
What is the output of the following code?
class Base {
static void greet() {
System.out.println("Hello from Base");
}
}
class Derived extends Base {
static void greet() {
System.out.println("Hello from Derived");
}
}
public class Main {
public static void main(String[] args) {
Base obj = new Derived();
obj.greet();
}
}Hello from Base
Static methods are not subject to runtime polymorphism — they are resolved at compile time based on the reference type, not the object type. This is called method hiding, not method overriding. Since the reference type is Base, Base.greet() is called. This is why the @Override annotation would cause a compilation error if applied to a static method — you cannot override static methods, only hide them. This is a frequently asked interview question to test understanding of static vs instance method dispatch.
What is the output of the following code?
class Calculator {
int add(int a, int b) { return a + b; }
double add(int a, int b) { return a + b; }
}Compilation error — methods cannot be overloaded by return type alone
This is a compilation error. In Java, method overloading requires methods to differ in their parameter lists (type, number, or order of parameters). The return type alone is not sufficient to distinguish overloaded methods. The compiler determines which overloaded method to call based on the arguments passed, and since both methods accept (int, int), the compiler cannot resolve the ambiguity. This is different from covariant return types in overriding, where the return type can vary as long as it is a subtype of the parent's return type.
What is the output of the following code?
class Parent {
int x = 10;
Parent() {
System.out.println("Parent: " + x);
}
}
class Child extends Parent {
int x = 20;
Child() {
super();
System.out.println("Child: " + x);
}
}
new Child();Parent: 10\nChild: 20
When new Child() is called, the Child constructor first invokes super(), which runs the Parent constructor. Inside the Parent constructor, x refers to the Parent's field (10), so it prints "Parent: 10". Then control returns to the Child constructor, where x refers to the Child's field (20), so it prints "Child: 20". Field access is resolved based on the reference type (compile time), unlike method calls which are resolved at runtime. This is called field hiding, not field overriding — Java does not support field overriding.
What is the output of the following code?
class Vehicle {
String type = "Vehicle";
String getType() { return "Vehicle"; }
}
class Car extends Vehicle {
String type = "Car";
String getType() { return "Car"; }
}
public class Main {
public static void main(String[] args) {
Vehicle v = new Car();
System.out.println(v.type + " " + v.getType());
}
}Vehicle Car
This is a classic interview question that tests the difference between field access and method access. Field access (v.type) is resolved at compile time based on the reference type (Vehicle), so it prints "Vehicle". Method calls (v.getType()) are resolved at runtime based on the actual object type (Car), so it prints "Car". This gives us "Vehicle Car". Fields are never polymorphic in Java — only instance methods are. This is why accessing fields directly (rather than through getter methods) can lead to surprising behavior in inheritance hierarchies.
What is the output of the following code?
class Base {
Base() {
print();
}
void print() {
System.out.println("Base");
}
}
class Derived extends Base {
int x = 10;
void print() {
System.out.println("Derived: " + x);
}
}
new Derived();Derived: 0
This is a subtle and dangerous pattern. When new Derived() is called, the Base constructor runs first and calls print(). Because print() is overridden in Derived and the actual object is a Derived instance, runtime polymorphism kicks in and calls Derived.print(). However, at this point the Derived constructor has not yet run, so x still has its default value of 0 (not 10). This prints "Derived: 0". This is why calling overridable methods from constructors is considered a code smell — the subclass fields are not yet initialized, leading to bugs. Effective Java (Item 19) warns explicitly against this pattern.
Which of the following is true about abstract classes and interfaces in Java?
An abstract class can have constructors; an interface cannot
Abstract classes can have constructors (which are called when a concrete subclass is instantiated), instance fields, and both abstract and concrete methods. Interfaces cannot have constructors or instance fields (only public static final constants). A class can extend only one abstract class but implement multiple interfaces. Since Java 8, interfaces can have default and static methods, and since Java 9, private methods — but they still cannot have constructors or mutable instance state.
What does the final keyword prevent when applied to a class, a method, and a variable respectively?
Inheritance, overriding, reassignment
A final class cannot be extended (e.g., String, Integer). A final method cannot be overridden by subclasses. A final variable cannot be reassigned after initialization. A common misconception is that a final reference variable makes the object immutable — it does not. A final List reference cannot point to a different list, but the list contents can still be modified. True immutability requires making the class final, all fields private and final, and not providing setters.
How does Java handle the diamond problem with interfaces?
If two interfaces provide conflicting default methods, the implementing class must override the method to resolve the ambiguity
The diamond problem occurs when a class inherits conflicting implementations from multiple sources. Java avoids this for classes by allowing only single inheritance. However, since Java 8 introduced default methods in interfaces, a class implementing two interfaces with the same default method faces a conflict. The compiler requires the class to explicitly override the conflicting method. Inside the override, you can delegate to a specific interface's implementation using InterfaceA.super.method(). This design keeps resolution explicit and compile-time safe.
What is the output of the following code?
interface Printable {
default void print() {
System.out.println("Printable");
}
}
interface Showable {
default void print() {
System.out.println("Showable");
}
}
class Document implements Printable, Showable {
public void print() {
Printable.super.print();
}
}
new Document().print();Printable
When a class implements two interfaces with the same default method, the class must override that method to resolve the conflict — otherwise it will not compile. In this case, Document overrides print() and explicitly delegates to Printable.super.print(), which prints "Printable". The InterfaceName.super.method() syntax is the mechanism Java provides for resolving diamond problem conflicts with default methods. If the override were removed, the compiler would report an error: "class Document inherits unrelated defaults for print() from types Printable and Showable."
What is the output of the following code?
class A {
int show() { return 10; }
}
class B extends A {
Integer show() { return 20; }
}
public class Main {
public static void main(String[] args) {
A obj = new B();
System.out.println(obj.show());
}
}Compilation error
This causes a compilation error because int and Integer are not covariant return types in this context. Covariant return types in Java allow a subclass to return a more specific type than the parent's return type, but this only applies to reference types (e.g., returning Dog instead of Animal). Primitive types like int and their wrapper types like Integer are not in a parent-child relationship for covariant returns. If class A returned Number and class B returned Integer, that would be a valid covariant return type since Integer extends Number.
Why should you prefer composition over inheritance?
Composition provides greater flexibility by allowing behavior to be changed at runtime, avoids tight coupling, and does not break encapsulation
Composition ("has-a" relationship) is generally preferred over inheritance ("is-a" relationship) because: (1) It avoids tight coupling — changing a superclass can break subclasses (the fragile base class problem). (2) It supports runtime flexibility — you can swap composed objects at runtime. (3) It does not violate encapsulation — inheritance exposes the parent's implementation details. (4) It avoids deep, rigid class hierarchies. The classic example is the java.util.Stack class, which extends Vector instead of composing it, allowing callers to insert elements at arbitrary positions — a design flaw that breaks the stack abstraction.
Which SOLID principle does the following code violate?
class UserService {
void createUser(User user) { /* DB insert */ }
void sendWelcomeEmail(User user) { /* send email */ }
void generateReport(User user) { /* PDF report */ }
}Single Responsibility Principle
The Single Responsibility Principle (SRP) states that a class should have only one reason to change. This UserService has three responsibilities: user persistence, email sending, and report generation. Changes to email templates, database schema, or report format would all require modifying this single class. The fix is to split it into UserRepository, EmailService, and ReportService. SRP is often misunderstood as "a class should only do one thing" — more accurately, it means a class should have only one actor or stakeholder whose requirements drive changes.
Which SOLID principle is violated when a subclass throws an exception that the parent class does not?
class Bird {
void fly() { /* flies */ }
}
class Penguin extends Bird {
void fly() {
throw new UnsupportedOperationException();
}
}Liskov Substitution Principle
The Liskov Substitution Principle (LSP) states that objects of a subclass should be replaceable for objects of the superclass without altering the correctness of the program. A Penguin that throws an exception when fly() is called violates this principle — any code that expects a Bird to fly would break. The fix is to restructure the hierarchy: create a FlyingBird interface or abstract class, and have only birds that can fly implement it. This example also demonstrates how the Interface Segregation Principle (ISP) can work alongside LSP — splitting Bird into finer-grained abstractions avoids the problem.
How does the Dependency Inversion Principle (DIP) improve code design?
class OrderService {
private MySQLDatabase db = new MySQLDatabase();
void saveOrder(Order order) {
db.save(order);
}
}High-level modules should depend on abstractions, not on low-level modules — OrderService should depend on a Database interface, not MySQLDatabase directly
The Dependency Inversion Principle states: (1) High-level modules should not depend on low-level modules — both should depend on abstractions. (2) Abstractions should not depend on details — details should depend on abstractions. In this example, OrderService (high-level) directly depends on MySQLDatabase (low-level). Applying DIP, you would create a Database interface, have MySQLDatabase implement it, and inject the dependency: OrderService(Database db). This decouples OrderService from any specific database, making it easy to swap implementations (e.g., PostgresDatabase, InMemoryDatabase for testing) without changing OrderService.
Which of the following correctly describes the Open/Closed Principle (OCP) and its application?
class DiscountCalculator {
double calculate(String type, double price) {
if (type.equals("REGULAR")) return price * 0.1;
if (type.equals("PREMIUM")) return price * 0.2;
if (type.equals("VIP")) return price * 0.3;
return 0;
}
}OCP states classes should be open for extension but closed for modification — this code violates it because adding a new discount type requires modifying the existing class
The Open/Closed Principle states that software entities should be open for extension but closed for modification. The DiscountCalculator violates OCP because adding a new customer type (e.g., "EMPLOYEE") requires modifying the existing calculate method. The fix is to use polymorphism: define a DiscountStrategy interface with a calculate(double price) method, and create separate implementations (RegularDiscount, PremiumDiscount, VipDiscount). New discount types can then be added by creating new classes without modifying existing code. This is the Strategy design pattern in action — a common way to achieve OCP compliance.
What does the instanceof operator return when used with a null reference?
false
The instanceof operator always returns false when the left operand is null. It never throws a NullPointerException. This makes it safe to use as a null check combined with a type check in a single expression: if (obj instanceof String s) (pattern matching, Java 16+) is both a null check and a type check. This is by design in the JLS (Java Language Specification) — null is not an instance of any type.
Take the interactive quiz and get your score with a personalized topic breakdown.
Start the Quiz20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
DevOps20 questions · 30 min
Join thousands of developers mastering in-demand skills with Amigoscode. Try it free today.