OOPs interview questions for freshers
The basics placement rounds start with: what OOP is, classes and objects, access modifiers, two output questions.
What is OOPs (object-oriented programming)?
Object-oriented programming (OOP) structures a program around objects: bundles of data (fields) and the methods that work on that data. A class describes the shape; each object is one instance with its own state.
public class Main {
public static void main(String[] args) {
Counter a = new Counter("clicks");
Counter b = new Counter("views");
a.increment();
a.increment();
b.increment();
System.out.println(a + ", " + b);
}
}
class Counter {
private final String name;
private int value;
Counter(String name) { this.name = name; }
void increment() { value++; }
@Override
public String toString() { return name + "=" + value; }
}One class, two objects counting separately: it prints clicks=2, views=1.
What is the difference between object-oriented and procedural programming?
Procedural code is functions that operate on data passed to them; object-oriented code keeps data and the functions that change it together in objects. The difference shows when you add a new kind of thing: procedural code edits every function that switches on the type, OOP adds one class.
What is the difference between a class and an object?
A class is a blueprint that declares fields and methods and holds no per-object data (only its static fields). An object is an instance created from it at runtime (with new in Java), with its own copy of the instance fields. Syntax in Java classes.
What are the four pillars of OOPs?
Encapsulation, abstraction, inheritance and polymorphism.
- Encapsulation: private fields changed only through methods that enforce the rules.
- Abstraction: expose what an object does, hide how.
- Inheritance: a class extends another in an is-a relation (
class Dog extends Animal). - Polymorphism: one call, behavior chosen by the object (
shape.area()).
What are the advantages and disadvantages of OOP?
OOP makes large programs easier to change because each class owns its data and rules, but it adds ceremony small programs do not need. Gains: modularity, reuse, new types without editing callers, and testing with fakes. Costs: more indirection, fragile deep inheritance trees, and shared mutable objects that complicate concurrency.
Is Java a pure object-oriented language?
No. Java's eight primitive types (int, double, boolean and so on) are not objects, and static members belong to no object. Wrapper classes (Integer) and autoboxing bridge the gap. Smalltalk is pure OOP, and Python is close, since even integers and functions are objects:
print((5).bit_length(), type(len).__name__, isinstance(len, object))It prints 3 builtin_function_or_method True.
What is the difference between a method and a function?
A method is a function that belongs to a class and runs in the context of an object (or of the class, if static); a function stands alone. In Java every function is a method. Python has both: len(items) is a function, items.append(3) a method that receives items as self.
What are access modifiers in OOPs?
Access modifiers decide which code may use a class member. Java has four: private (the class only), package-private with no keyword (the package), protected (the package plus subclasses elsewhere) and public (everywhere). Keep fields private. Python has only conventions (_name) and name mangling (__name). Details in Java access modifiers.
What does this print: a static counter shared by three objects?
public class Main {
public static void main(String[] args) {
Student first = new Student();
new Student();
Student third = new Student();
System.out.println(Student.count + " " + first.id + " " + third.id);
}
}
class Student {
static int count = 0;
int id;
Student() {
count++;
id = count;
}
}Predict the output
count is static, so the three objects share one copy and it ends at 3. id is an instance field, so each object keeps the value count had when it was built: 1 for the first, 3 for the third. A static method could read count but not id, because it has no this.
What does this print: == vs equals() on strings?
public class Main {
public static void main(String[] args) {
String a = "hi";
String b = "hi";
String c = new String("hi");
System.out.println((a == b) + " " + (a == c) + " " + a.equals(c));
}
}Predict the output
== compares references, equals() compares content. String literals are interned, so a and b are the same pooled object and a == b is true. new String("hi") always creates a new object, so a == c is false while a.equals(c) is true. Compare objects with equals(); use == for primitives, enums and deliberate identity checks.
The four pillars of OOPs: encapsulation, abstraction, inheritance, polymorphism
One question per pillar with runnable code, plus the comparisons that test whether you understand them.
What is encapsulation? Explain with an example.
Encapsulation bundles data with the methods that use it and hides the data behind them, so the object enforces its own rules: private fields, methods that validate every change.
public class Main {
public static void main(String[] args) {
BankAccount acc = new BankAccount(100);
acc.withdraw(30);
System.out.println(acc.getBalance());
try {
acc.withdraw(500);
} catch (IllegalArgumentException e) {
System.out.println("Refused: " + e.getMessage());
}
System.out.println(acc.getBalance());
}
}
class BankAccount {
private int balance;
BankAccount(int opening) { balance = opening; }
int getBalance() { return balance; }
void withdraw(int amount) {
if (amount <= 0 || amount > balance) {
throw new IllegalArgumentException("invalid amount " + amount);
}
balance -= amount;
}
}It prints 70, then Refused: invalid amount 500, then 70. A public setBalance() would let callers skip the rule, so getters and setters for everything are not encapsulation.
What is the difference between abstraction and encapsulation?
Abstraction hides complexity by showing only what an object does; encapsulation hides state by controlling who reads and changes it. Abstraction is the contract (interfaces, abstract classes), encapsulation the implementation (private fields, access modifiers). List lets you add and get without knowing it is an array; ArrayList keeps that array private.
What is inheritance, and what types of inheritance does Java support?
Inheritance lets a subclass reuse and extend the fields and methods of a superclass, modelling an is-a relation: a Truck is a Vehicle. A Java class extends one class but can implement many interfaces, which since Java 8 can carry default methods. Python and C++ allow several superclasses.
public class Main {
public static void main(String[] args) {
System.out.println(new Truck("MH12", 8).info());
}
}
class Vehicle {
protected final String plate;
Vehicle(String plate) { this.plate = plate; }
String info() { return "Vehicle " + plate; }
}
class Truck extends Vehicle {
private final int tons;
Truck(String plate, int tons) {
super(plate);
this.tons = tons;
}
@Override
String info() { return super.info() + ", " + tons + " t"; }
}It prints Vehicle MH12, 8 t. More in Java inheritance.
What is polymorphism? What are its types?
Polymorphism means one interface, many implementations: the same call does different work depending on the object. Compile-time polymorphism is overloading, picked by the compiler from the declared argument types; runtime polymorphism is overriding, picked from the object's actual class.
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Shape> shapes = List.of(new Circle(1), new Square(2));
for (Shape s : shapes) {
System.out.printf("%s %.2f%n", s.getClass().getSimpleName(), s.area());
}
}
}
interface Shape { double area(); }
record Circle(double r) implements Shape {
public double area() { return Math.PI * r * r; }
}
record Square(double side) implements Shape {
public double area() { return side * side; }
}It prints Circle 3.14 and Square 4.00; the loop never checks the type. See Java polymorphism.
What is the difference between static binding and dynamic binding?
Static (early) binding resolves a call at compile time; dynamic (late) binding resolves it at runtime from the object's actual class. In Java, overloads, static and private methods, constructors and field access are bound statically, so fields are not polymorphic. Overridable instance methods are dynamic: Animal a = new Dog(); a.sound(); runs Dog.sound().
What does this print: a getter that returns a private final list?
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
Team team = new Team();
team.add("Asha");
team.getMembers().add("Intruder");
System.out.println(team.getMembers().size());
}
}
class Team {
private final List<String> members = new ArrayList<>();
void add(String name) { members.add(name); }
List<String> getMembers() { return members; }
}Predict the output
final fixes the reference, not the list. getMembers() hands out the internal list itself, so the caller adds "Intruder" straight into the team and the size is 2, even though the field is private final. Return a copy instead:
List<String> getMembers() { return List.copyOf(members); }
Now the caller's add throws UnsupportedOperationException.
Classes, objects and constructors interview questions
Constructors, initialization order, this and super, static members, copying and cleanup.
What is a constructor, and how is it different from a method?
A constructor runs once when an object is created and puts it into a valid starting state. It has the class's name, no return type (not even void), and is not inherited. The trap: Java adds a no-argument default constructor only when you write none, so after adding Point(int x, int y), new Point() stops compiling. See Java constructors.
What is constructor overloading and constructor chaining?
Constructor overloading is several constructors with different parameter lists. Chaining is one constructor calling another with this(...), or the parent's with super(...), so the setup lives in one place.
public class Main {
public static void main(String[] args) {
System.out.println(new Pizza() + " " + new Pizza("large") + " " + new Pizza("small", "thick"));
}
}
class Pizza {
final String size;
final String crust;
Pizza() { this("medium"); }
Pizza(String size) { this(size, "thin"); }
Pizza(String size, String crust) {
this.size = size;
this.crust = crust;
}
@Override
public String toString() { return size + "/" + crust; }
}It prints medium/thin large/thin small/thick. Before Java 25 the call must be the first statement (Java 22 to 24 only previewed the change); Java 25 allows code before it that does not read the object being built, see JEP 513.
What are the types of constructors? Does Python have a constructor?
Default (no arguments), parameterized and copy (built from another object of the same class). Java and C++ add a default constructor only if you write none. C++ generates a memberwise copy constructor unless you declare your own, and deletes it if you declare a move operation; Java has none, so you write Point(Point other). Python has one __init__, which strictly is the initializer: __new__ creates the object.
What does this print: constructors in a three-level class hierarchy?
public class Main {
public static void main(String[] args) {
new C();
}
}
class A { A() { System.out.print("A "); } }
class B extends A { B() { System.out.print("B "); } }
class C extends B { C() { System.out.print("C"); } }Predict the output
Every constructor starts with an implicit super(), so C() first calls B(), which first calls A(). The parent is built before the child adds its part. C++ destructors run the other way, child first.
What does this print: static and instance initializer blocks with two objects?
public class Main {
public static void main(String[] args) {
new Box();
new Box();
}
}
class Box {
static { System.out.print("static "); }
{ System.out.print("init "); }
Box() { System.out.print("ctor "); }
}Predict the output
A static initializer runs once, when the class is initialized on first use; an instance initializer runs for every object, before the constructor body. So the first new Box() prints all three and the second only init ctor.
Can a constructor be private? When would you make one private?
Yes. A private constructor means only the class itself can create instances. Uses: a singleton, static factories (LocalDate.of(2026, 1, 15) decides what to return), utility classes such as java.lang.Math, and builders. A class whose constructors are all private cannot be subclassed outside the top-level class that contains it, since a subclass constructor must call super(...).
What are the this and super keywords used for?
this refers to the current object; super refers to the parent class's part of it. this tells a field from a parameter (this.name = name;) and calls another constructor (this(size, "thin");). super calls the parent constructor or the parent version of an overridden method (super.info()). Neither exists in a static method.
What is the difference between static and instance members?
An instance member belongs to each object, which has its own copy of every instance field; a static member belongs to the class and is shared by all objects. Static members are reached through the class (Math.max(...)), have no this, and suit counters, constants and utility methods. More in Java static members.
Which methods does every Java class inherit from Object?
Every class inherits equals(), hashCode(), toString(), getClass(), clone(), wait(), notify(), notifyAll() and finalize() from java.lang.Object. Override equals() and hashCode() together or hash collections break; the default toString() prints something like Point@1b6d3586; finalize() is deprecated for removal since Java 18 (JEP 421).
Does Java have destructors? How is cleanup done?
No. The garbage collector frees memory when an object becomes unreachable, at a time you do not control. For files, sockets and connections, implement AutoCloseable and use try-with-resources, which calls close() deterministically:
public class Main {
public static void main(String[] args) {
try (Resource a = new Resource("db"); Resource b = new Resource("file")) {
System.out.println("working");
}
}
}
class Resource implements AutoCloseable {
private final String name;
Resource(String name) {
this.name = name;
System.out.println("open " + name);
}
@Override
public void close() { System.out.println("close " + name); }
}It prints open db, open file, working, close file, close db: resources close in reverse order. See Java try-catch.
What is the difference between a shallow copy and a deep copy?
A shallow copy copies the fields as they are, so referenced objects are shared; a deep copy also copies the referenced objects, so the two are independent.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
Order original = new Order(new ArrayList<>(List.of("tea")));
Order shallow = new Order(original, false);
Order deep = new Order(original, true);
original.items.add("cake");
System.out.println(shallow.items + " " + deep.items);
}
}
class Order {
final List<String> items;
Order(List<String> items) { this.items = items; }
Order(Order other, boolean deep) {
this.items = deep ? new ArrayList<>(other.items) : other.items;
}
}It prints [tea, cake] [tea]: the shallow copy saw the change. Object.clone() is shallow, so prefer a copy constructor.
How do you model a linked list node or a tree node as a class?
A node is a small class holding a value and references to other nodes: next for a linked list, left and right for a binary tree.
public class Main {
public static void main(String[] args) {
Node head = new Node(1, new Node(2, new Node(3, null)));
Node prev = null;
while (head != null) {
Node next = head.next;
head.next = prev;
prev = head;
head = next;
}
for (Node n = prev; n != null; n = n.next) {
System.out.print(n.val + " ");
}
System.out.println();
}
}
class Node {
int val;
Node next;
Node(int val, Node next) {
this.val = val;
this.next = next;
}
}It reverses 1 -> 2 -> 3 by re-pointing references and prints 3 2 1. Most linked list and tree problems start with this class.
Inheritance and polymorphism interview questions
Overloading versus overriding, what is polymorphic in Java, the diamond problem, and the output questions most candidates miss.
What is the difference between method overloading and method overriding?
Overloading is several methods with the same name and different parameter lists, chosen at compile time from the argument types. Overriding is a subclass replacing an inherited method with the same signature, chosen at runtime from the object.
public class Main {
public static void main(String[] args) {
Printer p = new LoudPrinter();
p.print(7);
p.print("hi");
}
}
class Printer {
void print(int x) { System.out.println("int " + x); }
void print(String s) { System.out.println("String " + s); }
}
class LoudPrinter extends Printer {
@Override
void print(String s) { System.out.println("STRING " + s.toUpperCase()); }
}It prints int 7 and STRING HI. See method overloading.
What is operator overloading? Does Java support it?
Operator overloading lets a class define what an operator such as + or == does for its objects. C++ (operator+) and Python (__add__, __eq__) support it. Java does not, apart from the built-in + on strings, so you call methods such as BigDecimal.add.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return isinstance(other, Vector) and (self.x, self.y) == (other.x, other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
print(Vector(1, 2) + Vector(3, 4), Vector(1, 2) == Vector(1, 2))It prints Vector(4, 6) True. See C++ operator overloading.
What does this print: a field redeclared in a subclass, read through a parent reference?
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
System.out.println(a.name + " " + a.getName());
}
}
class Animal {
String name = "Animal";
String getName() { return name; }
}
class Dog extends Animal {
String name = "Dog";
@Override
String getName() { return name; }
}Predict the output
Methods are polymorphic, fields are not. a.name is resolved at compile time from the reference type Animal, while a.getName() is overridden and dispatched at runtime to Dog. A subclass field with the same name hides the parent's, and both exist in the object. It compiles, and it is almost always a bug.
What does this print: a static and an instance method called through a parent reference?
public class Main {
public static void main(String[] args) {
Parent p = new Child();
System.out.println(p.who() + " " + p.me());
}
}
class Parent {
static String who() { return "Parent"; }
String me() { return "Parent"; }
}
class Child extends Parent {
static String who() { return "Child"; }
@Override
String me() { return "Child"; }
}Predict the output
Static methods are hidden, not overridden. A static call binds at compile time to the reference type, so p.who() calls Parent.who(), while the instance method me() is dispatched at runtime to Child. @Override on a static method is a compile error.
What does this print: null passed to overloads for Object and String?
public class Main {
static void show(Object o) { System.out.println("Object"); }
static void show(String s) { System.out.println("String"); }
public static void main(String[] args) {
show(null);
}
}Predict the output
null matches both overloads, and Java picks the most specific: String is a subtype of Object, so show(String) wins.
Follow-up: what if you add show(Integer i)? The call stops compiling as ambiguous, because neither String nor Integer is more specific; choose with show((Integer) null).
Why does Java not support multiple inheritance of classes? What is the diamond problem?
If D inherits from B and C, and both override a method of A, which version does D get? Java avoids this for classes by allowing one superclass. Interface default methods can still clash, and then the class must override and choose:
public class Main {
public static void main(String[] args) {
System.out.println(new SmartPhone().click());
}
}
interface Camera {
default String click() { return "photo"; }
}
interface Phone {
default String click() { return "call"; }
}
class SmartPhone implements Camera, Phone {
@Override
public String click() { return Camera.super.click() + " + " + Phone.super.click(); }
}It prints photo + call; without the override it does not compile. Python resolves the diamond with the MRO.
What does this print: an overridden method called from the parent constructor?
public class Main {
public static void main(String[] args) {
new Derived();
}
}
class Base {
Base() { describe(); }
void describe() { System.out.println("base"); }
}
class Derived extends Base {
String name = "derived";
@Override
void describe() { System.out.println("name=" + name); }
}Predict the output
Base's constructor runs before Derived's field initializers, but the object is already a Derived, so describe() dispatches to the override and reads name while it is still null. C++ differs: during Base's constructor a virtual call runs Base::describe(). The rule from Effective Java: constructors must not call overridable methods.
What is upcasting and downcasting? What does instanceof do?
Upcasting treats a subclass object as its parent type (Animal a = new Dog();) and is always safe. Downcasting goes back (Dog d = (Dog) a;) and throws ClassCastException if the object is not that type. instanceof checks first, and since Java 16 binds a variable:
public class Main {
public static void main(String[] args) {
Object[] things = { "hello", 42, 3.5 };
for (Object o : things) {
if (o instanceof String s) {
System.out.println("String of length " + s.length());
} else if (o instanceof Integer i) {
System.out.println("Integer doubled " + (i * 2));
} else {
System.out.println("Other: " + o.getClass().getSimpleName());
}
}
}
}It prints String of length 5, Integer doubled 84, Other: Double.
What does final mean on a variable, a method and a class?
final means it cannot change further. A final variable is assigned once, a final method cannot be overridden, and a final class cannot be extended (String, Integer). The trap: a final reference is not an immutable object. final List<String> list cannot point to another list, but list.add("x") still works.
What is a covariant return type?
A covariant return type lets an overriding method return a subtype of the parent method's return type (Java 5 and later).
class Animal {
Animal reproduce() { return new Animal(); }
}
class Dog extends Animal {
@Override
Dog reproduce() { return new Dog(); } // Dog is a subtype of Animal
}
Callers holding a Dog get a Dog back without a cast. Parameters have no such freedom: changing a parameter type creates an overload, not an override.
Abstraction and interfaces interview questions
Abstract classes, interfaces, functional and marker interfaces, and the Python equivalents.
What is abstraction in OOPs? Give a real example.
Abstraction is showing what an object does and hiding how, so callers depend on a simple contract. With List<String> names = new ArrayList<>(); you call add and get without caring how items are stored. Interfaces and abstract classes are the tools, and the hidden part can change without breaking callers.
What is the difference between an abstract class and an interface?
An abstract class is a partly built class that can hold state and constructors; an interface is a contract, and a class can implement many interfaces but extend one class.
| Abstract class | Interface | |
|---|---|---|
| Methods | Abstract and concrete | Abstract; default and static since Java 8, private since Java 9 |
| Fields | Any instance fields | Only public static final constants |
| Constructors | Yes | No |
See abstract classes.
When should you use an abstract class instead of an interface?
Use an interface for a capability that unrelated classes share, and an abstract class when related classes share state and code. A String and an Employee can both be Comparable; a family with common fields and a fixed algorithm fits an abstract class. Default to an interface, which stays open to any class and to lambdas. More in Java interfaces.
What does this print: an abstract class with its own constructor?
public class Main {
public static void main(String[] args) {
Shape s = new Square(2);
System.out.print(s.area());
}
}
abstract class Shape {
Shape() { System.out.print("Shape "); }
abstract double area();
}
class Square extends Shape {
private final double side;
Square(double side) {
this.side = side;
System.out.print("Square ");
}
@Override
double area() { return side * side; }
}Predict the output
An abstract class cannot be created with new Shape(), but its constructor runs whenever a subclass is created. new Square(2) calls super() first, which prints Shape , then Square , and area() returns the double 4.0. Use that constructor for the fields every subclass shares.
Can an interface have fields or a constructor?
No constructor and no instance fields. Variables declared in an interface are implicitly public static final constants:
interface Limits {
int MAX_USERS = 100; // public static final, even without the keywords
void check(int users);
}
An interface has no state to initialize, and you never instantiate one directly. Implementing an interface only to use its constants is an anti-pattern; use a final class or an enum.
What is a functional interface?
A functional interface has exactly one abstract method, so a lambda or method reference can implement it. default and static methods do not count, nor do abstract redeclarations of Object's public methods, so Comparator (it declares equals) is functional.
public class Main {
@FunctionalInterface
interface Discount {
double apply(double price);
}
public static void main(String[] args) {
Discount none = p -> p;
Discount festive = p -> p * 0.8;
Discount flat = p -> Math.max(0, p - 50);
System.out.println(none.apply(200) + " " + festive.apply(200) + " " + flat.apply(200));
}
}It prints 200.0 160.0 150.0. More in Java lambdas.
What is a marker interface?
A marker interface has no methods; implementing it tags the class for an instanceof check. Serializable is the standard example: ObjectOutputStream refuses objects that do not implement it. Annotations are the newer way to attach metadata, but a marker interface is a type, so the compiler can check void save(Serializable s).
How do you create an abstract class in Python?
Inherit from abc.ABC and mark the required methods with @abstractmethod; Python then refuses to instantiate any class that still has an abstract method.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
...
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
print(Square(3).area())
try:
Shape()
except TypeError as e:
print(e)It prints 9, then Can't instantiate abstract class Shape with abstract method area (Python 3.11's wording; 3.12 rephrased it). Unlike Java's compile-time check, this one happens when the object is created.
SOLID principles and design patterns interview questions
The five SOLID principles, composition over inheritance, and the common patterns: singleton, factory, strategy, observer, builder.
What are the SOLID principles?
Five design rules for classes that are easy to change:
- Single responsibility: one reason to change.
- Open/closed: add behavior with new classes, not edits.
- Liskov substitution: a subclass works anywhere its parent does.
- Interface segregation: no class implements methods it does not need.
- Dependency inversion: depend on abstractions, not concrete classes.
Explain the single responsibility principle with an example.
A class should have one reason to change. If tax rules, PDF layout and the database schema can each force edits to Invoice, it has three responsibilities:
// Before: three reasons to change
class Invoice {
double total() { ... } // business rules
byte[] toPdf() { ... } // presentation
void saveToDb() { ... } // persistence
}
// After: one each
class Invoice { double total() { ... } }
class InvoicePdfRenderer { byte[] render(Invoice inv) { ... } }
class InvoiceRepository { void save(Invoice inv) { ... } }
Now a layout change cannot break the tax calculation. Split when concerns change for different reasons, not into one-method classes.
What is the open/closed principle?
Classes should be open for extension and closed for modification: you add behavior by writing a new class, not by editing one that works.
public class Main {
public static void main(String[] args) {
Checkout checkout = new Checkout();
System.out.println(checkout.complete(new Upi(), 499));
System.out.println(checkout.complete(new Card(), 1299));
}
}
interface PaymentMethod { String pay(int amount); }
class Upi implements PaymentMethod {
public String pay(int amount) { return "UPI paid " + amount; }
}
class Card implements PaymentMethod {
public String pay(int amount) { return "Card paid " + amount; }
}
class Checkout {
String complete(PaymentMethod method, int amount) { return method.pay(amount); }
}It prints UPI paid 499 and Card paid 1299. A wallet is one new class, and Checkout stays untouched. The violation is an if (type.equals("upi")) chain inside Checkout.
What does this print: a Square resized through a Rectangle method? Which SOLID principle does it break?
public class Main {
static void resize(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
System.out.println(r.area());
}
public static void main(String[] args) {
resize(new Square());
}
}
class Rectangle {
protected int width, height;
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
int area() { return width * height; }
}
class Square extends Rectangle {
@Override
void setWidth(int w) { width = w; height = w; }
@Override
void setHeight(int h) { width = h; height = h; }
}Predict the output
Square keeps itself square by setting both sides in each setter, so setHeight(4) also makes the width 4: the area is 16, not the 20 resize expects. That breaks the Liskov substitution principle: a subtype must honor its parent's behavior, not only its signatures. Fix it with immutable shapes, or a shared Shape interface.
What is the interface segregation principle?
No class should be forced to implement methods it does not use, so split a fat interface into small, focused ones:
// Fat: a robot must pretend to eat
interface Worker { void work(); void eat(); }
// Segregated
interface Workable { void work(); }
interface Eater { void eat(); }
class Human implements Workable, Eater { ... }
class Robot implements Workable { ... }
The symptom of a violation is an implementation that throws UnsupportedOperationException or leaves a method empty.
What is the difference between dependency inversion and dependency injection?
Dependency inversion is the principle: high-level code depends on an abstraction, not a concrete class. Dependency injection is one way to get there: the object receives its dependencies, usually through the constructor, instead of creating them with new.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
FakeNotifier fake = new FakeNotifier();
new OrderService(fake).place("asha");
System.out.println(fake.sent);
}
}
interface Notifier { void send(String to, String message); }
class FakeNotifier implements Notifier {
final List<String> sent = new ArrayList<>();
public void send(String to, String message) { sent.add(to + ": " + message); }
}
class OrderService {
private final Notifier notifier;
OrderService(Notifier notifier) { this.notifier = notifier; }
void place(String user) { notifier.send(user, "order placed"); }
}It prints [asha: order placed]; a test passes a fake sender.
What is composition, and why prefer composition over inheritance?
Composition builds a class from objects it holds (has-a) instead of extending one (is-a). Prefer it because inheritance exposes every public method of the parent and ties you to its implementation. java.util.Stack extends Vector, so it accepts list operations that break the stack:
import java.util.Stack;
public class Main {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.add(0, 99);
System.out.println(stack + " pop=" + stack.pop());
}
}It prints [99, 1, 2] pop=2. A stack that holds an ArrayDeque cannot be misused that way.
What is the difference between association, aggregation and composition?
They differ in ownership and lifetime. Association: objects use each other and live independently (a teacher and a student). Aggregation: has-a, and the parts can outlive the whole (a team and its players). Composition: the whole owns its parts, and they die with it (a house and its rooms).
What are coupling and cohesion?
Cohesion is how closely the parts of one class belong together; coupling is how much a class depends on another's internals. Aim for high cohesion and low coupling. OrderService creating new MySqlOrderDao() is tightly coupled; taking an OrderRepository interface in its constructor is loose.
How do you implement a thread-safe singleton in Java?
A singleton guarantees one instance with a global access point. The thread-safe versions are the enum singleton and the holder idiom; both rely on the JVM running class initialization once, under a lock.
public class Main {
public static void main(String[] args) {
System.out.println("start");
Config a = Config.get();
Config b = Config.get();
System.out.println(a == b);
}
}
class Config {
private Config() { System.out.println("loading config"); }
private static class Holder {
static final Config INSTANCE = new Config();
}
static Config get() { return Holder.INSTANCE; }
}It prints start, loading config, true: created lazily, once. Double-checked locking works only with a volatile field.
What is the factory design pattern?
A factory is a method or class that creates objects, so callers ask for a notification for a channel and never name the concrete class. It keeps new ConcreteClass() in one place.
import java.util.List;
public class Main {
public static void main(String[] args) {
for (String channel : List.of("email", "sms")) {
System.out.println(NotificationFactory.create(channel).send("OTP 4821"));
}
}
}
interface Notification { String send(String message); }
class EmailNotification implements Notification {
public String send(String message) { return "email: " + message; }
}
class SmsNotification implements Notification {
public String send(String message) { return "sms: " + message; }
}
class NotificationFactory {
static Notification create(String channel) {
return switch (channel) {
case "email" -> new EmailNotification();
case "sms" -> new SmsNotification();
default -> throw new IllegalArgumentException("unknown channel " + channel);
};
}
}It prints email: OTP 4821 and sms: OTP 4821. List.of and Integer.valueOf are static factories in the JDK.
What is the strategy pattern?
The strategy pattern puts interchangeable algorithms behind one interface, so the caller picks one at runtime instead of branching with if/else. With lambdas a strategy is one line:
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.DoubleUnaryOperator;
public class Main {
public static void main(String[] args) {
Map<String, DoubleUnaryOperator> shipping = new LinkedHashMap<>();
shipping.put("standard", kg -> 40 + 10 * kg);
shipping.put("express", kg -> 100 + 25 * kg);
shipping.put("pickup", kg -> 0);
for (var entry : shipping.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue().applyAsDouble(2));
}
}
}It prints standard 60.0, express 150.0 and pickup 0.0. Comparator is the strategy everyone uses: list.sort(byName) versus list.sort(byAge).
What is the observer pattern?
A subject keeps a list of listeners and notifies all of them when something happens, without knowing what they do.
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
OrderEvents events = new OrderEvents();
events.subscribe(id -> System.out.println("email sent for " + id));
events.subscribe(id -> System.out.println("stock updated for " + id));
events.publish("A17");
}
}
class OrderEvents {
private final List<Consumer<String>> listeners = new ArrayList<>();
void subscribe(Consumer<String> listener) { listeners.add(listener); }
void publish(String orderId) {
for (Consumer<String> listener : listeners) listener.accept(orderId);
}
}It prints email sent for A17 then stock updated for A17. Pitfall: listeners that are never removed leak memory.
What is the builder pattern, and when do you use it?
A builder sets an object's values through named calls and creates it in one build(). Use it when a class has many optional parameters, instead of telescoping constructors such as new User("Asha", null, null, 0, true).
public class Main {
public static void main(String[] args) {
UserProfile p = new UserProfile.Builder("Asha")
.email("asha@example.com")
.city("Pune")
.build();
System.out.println(p);
}
}
class UserProfile {
private final String name;
private final String email;
private final String city;
private UserProfile(Builder b) {
name = b.name;
email = b.email;
city = b.city;
}
@Override
public String toString() { return name + " <" + email + "> " + city; }
static class Builder {
private final String name;
private String email = "none";
private String city = "unknown";
Builder(String name) { this.name = name; }
Builder email(String email) { this.email = email; return this; }
Builder city(String city) { this.city = city; return this; }
UserProfile build() { return new UserProfile(this); }
}
}It prints Asha <asha@example.com> Pune. build() is the one place to validate, and the result can be immutable.
OOPs in Java vs Python vs C++
How the same ideas look in Java, Python and C++, with the Python traps as runnable code.
How is OOP different in Java, Python and C++?
They differ mostly in how strict they are.
| Java | Python | C++ | |
|---|---|---|---|
| Multiple inheritance of classes | No; many interfaces | Yes, by the MRO | Yes |
| Access control | Enforced | Conventions only | Enforced, plus friend |
| Methods dynamic by default | Yes | Yes | No, only virtual ones |
| Method overloading | Yes | No (last def wins) | Yes |
| Cleanup | Garbage collector | Reference counting plus a cycle collector (CPython) | Destructors (RAII) |
What is self in Python?
self is the first parameter of an instance method: the object the method was called on. Python passes it for you, so c.increment() is shorthand for Counter.increment(c).
class Counter:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
c = Counter()
c.increment()
Counter.increment(c)
print(c.value)It prints 2. self is a convention, not a keyword. See Python classes.
What does this print: single and double underscore attributes in Python?
class Account:
def __init__(self):
self.__pin = 1234
self._owner = "asha"
a = Account()
print(hasattr(a, "__pin"), a._owner, a._Account__pin)Predict the output
A name with two leading underscores inside a class is mangled to _ClassName__name, so self.__pin is stored as _Account__pin. hasattr(a, "__pin") is False, while a._Account__pin still works. A single underscore is only a convention, and nothing stops access. Mangling avoids name clashes in subclasses; it is not security.
What does this print: a list defined as a Python class attribute?
class Dog:
tricks = []
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
rex = Dog("Rex")
fido = Dog("Fido")
rex.add_trick("sit")
print(fido.tricks)Predict the output
tricks = [] is a class attribute: one list on the class, shared by every instance. self.tricks.append(...) finds that list and mutates it, so Fido sees Rex's trick. Create per-object state in __init__ (self.tricks = []); class attributes are safe for constants and values you only read.
What does this print: a Python diamond declared as D(B, C)?
class A:
def hi(self):
return "A"
class B(A):
def hi(self):
return "B"
class C(A):
def hi(self):
return "C"
class D(B, C):
pass
print(D().hi(), [k.__name__ for k in D.__mro__])Predict the output
Python resolves methods along the MRO (method resolution order), computed by C3 linearization: the class, then its parents left to right, with a shared base after every class that inherits from it. For D(B, C) that is D, B, C, A, object, so hi() comes from B. More in Python inheritance.
Does Python support method overloading?
Not the way Java does. A second def with the same name replaces the first, so only the last exists. Use default arguments, *args, or functools.singledispatch to choose by the type of the first argument (singledispatchmethod, Python 3.8+, does it for methods).
from functools import singledispatch
class Calc:
def add(self, a, b):
return a + b
def add(self, a, b, c=0): # replaces the method above
return a + b + c
print(Calc().add(1, 2), Calc().add(1, 2, 3))
@singledispatch
def describe(x):
return "something"
@describe.register
def _(x: int):
return f"int {x}"
@describe.register
def _(x: str):
return f"str {x!r}"
print(describe(5), describe("hi"), describe(2.5))It prints 3 6, then int 5 str 'hi' something.
What is @property in Python, and why use it instead of getters and setters?
@property (a decorator) makes a method read like an attribute, and @name.setter validates assignments. You can add a property later without changing any caller, so Python code does not write get_x() and set_x() up front.
class Temperature:
def __init__(self, celsius):
self.celsius = celsius # goes through the setter
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("below absolute zero")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9 / 5 + 32
t = Temperature(25)
print(t.fahrenheit)
try:
t.celsius = -300
except ValueError as e:
print("Refused:", e)It prints 77.0, then Refused: below absolute zero. fahrenheit has no setter, so it is read-only.
What is duck typing, and how is it different from using interfaces?
Duck typing accepts an object for what it can do, not the type it declares: if it has speak(), you can call speak(). Python checks at runtime; Java requires implements Speaker and checks at compile time.
class Duck:
def speak(self):
return "Quack"
class Robot:
def speak(self):
return "Beep"
def chorus(things):
return " ".join(t.speak() for t in things)
print(chorus([Duck(), Robot()]))It prints Quack Beep although Duck and Robot share no parent. A missing method fails only when that line runs.
What is a virtual function in C++? What happens without virtual?
A virtual function is dispatched at runtime from the object's actual type when called through a base pointer or reference. Without virtual, C++ binds the call at compile time from the pointer's type, the opposite of Java's default.
#include <iostream>
struct Animal {
void name() { std::cout << "Animal\n"; } // not virtual
virtual void sound() { std::cout << "...\n"; }
virtual ~Animal() = default;
};
struct Dog : Animal {
void name() { std::cout << "Dog\n"; }
void sound() override { std::cout << "Woof\n"; }
};
int main() {
Dog d;
Animal& a = d;
a.name(); // prints Animal: bound at compile time
a.sound(); // prints Woof: dispatched at runtime
}
A pure virtual function (= 0) makes the class abstract. See C++ virtual functions.
Why does a C++ base class need a virtual destructor?
Deleting a derived object through a base class pointer whose destructor is not virtual is undefined behavior; usually the derived destructor does not run, so its resources leak.
struct Base {
~Base() {} // should be: virtual ~Base() = default;
};
struct Buffer : Base {
int* data = new int[1000];
~Buffer() { delete[] data; }
};
int main() {
Base* p = new Buffer();
delete p; // undefined behavior: ~Buffer() is not called
}
A base class destructor should be either public and virtual, or protected and non-virtual. See the cppreference delete expression page.
What is the difference between a struct and a class in C++?
The only difference is default access: members and base classes of a struct are public by default, those of a class are private. Both can have constructors, methods, inheritance and virtual functions.
struct Point { int x, y; }; // x and y are public
class Account { int balance; }; // balance is private
By convention struct holds plain data and class holds types with invariants.
What is a friend function in C++? Does it break encapsulation?
A friend function (or class) is outside code that a class explicitly allows to read its private and protected members. It is not a member and has no this.
#include <iostream>
class Money {
long paise;
public:
explicit Money(long p) : paise(p) {}
friend std::ostream& operator<<(std::ostream& out, const Money& m);
};
std::ostream& operator<<(std::ostream& out, const Money& m) {
return out << m.paise / 100 << "." << m.paise % 100 / 10 << m.paise % 10;
}
The classic use is operator<<, whose left operand is the stream, so it cannot be a member. Friendship is granted by the class and is neither transitive nor inherited.
OOPs interview questions for experienced developers
Equality contracts, immutability, records and sealed types, dispatch internals and object-oriented design rounds.
What does this print: equal Point objects added to a HashSet? What is the equals and hashCode contract?
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
System.out.println(set.size() + " " + new Point(1, 2).equals(new Point(1, 2)));
}
}
class Point {
final int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
return o instanceof Point p && p.x == x && p.y == y;
}
}Predict the output
It prints 2 true. hashCode() was not overridden, so the equal points keep their identity hashes; with different hash codes HashSet never calls equals() and stores both. (A rare identity hash collision would give 1.) The contract: equal objects must have equal hash codes, and both methods must stay stable while the object is a key. Override them together, or use a record. See Java HashMap.
What is a record in Java, and when should you use one?
A record (final since Java 16) is a class for immutable data: you declare the components, and the compiler generates the private final fields, constructor, accessors (x(), not getX()), equals(), hashCode() and toString().
public class Main {
record Point(int x, int y) {
Point {
if (x < 0 || y < 0) throw new IllegalArgumentException("negative");
}
double distance() { return Math.sqrt(x * x + y * y); }
}
public static void main(String[] args) {
Point a = new Point(3, 4);
System.out.println(a + " " + a.equals(new Point(3, 4)) + " " + a.distance());
}
}It prints Point[x=3, y=4] true 5.0. Records are only shallowly immutable: a List component can still change unless you copy it.
How do you create an immutable class in Java?
Make the class final (or its constructors private), every field private final, provide no setters (modifying methods return a new object), and copy mutable inputs and outputs so outside code never holds your internal state.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> source = new ArrayList<>(List.of("a"));
Playlist p = new Playlist(source);
source.add("b");
Playlist q = p.with("c");
System.out.println(p.songs() + " " + q.songs());
try {
p.songs().add("x");
} catch (UnsupportedOperationException e) {
System.out.println("read-only");
}
}
}
final class Playlist {
private final List<String> songs;
Playlist(List<String> songs) { this.songs = List.copyOf(songs); }
List<String> songs() { return songs; }
Playlist with(String song) {
List<String> next = new ArrayList<>(songs);
next.add(song);
return new Playlist(next);
}
}It prints [a] [a, c], then read-only. Immutable objects are thread-safe without locks and safe as map keys.
What is the difference between Comparable and Comparator?
Comparable gives a class one natural order through compareTo(), written inside the class. A Comparator is a separate object for any other order.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Main {
record Student(String name, int marks) implements Comparable<Student> {
public int compareTo(Student other) { return Integer.compare(marks, other.marks); }
}
public static void main(String[] args) {
List<Student> list = new ArrayList<>(List.of(
new Student("Ravi", 72), new Student("Asha", 91), new Student("Meera", 72)));
Collections.sort(list);
System.out.println(list.stream().map(Student::name).toList());
list.sort(Comparator.comparingInt(Student::marks).reversed().thenComparing(Student::name));
System.out.println(list.stream().map(Student::name).toList());
}
}It prints [Ravi, Meera, Asha] (by marks; the stable sort keeps the two 72s in order), then [Asha, Meera, Ravi]. Use Integer.compare rather than a - b, which overflows.
What does this print: a HashSet subclass that counts the elements added?
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
public class Main {
public static void main(String[] args) {
CountingSet<String> set = new CountingSet<>();
set.addAll(List.of("a", "b", "c"));
System.out.println(set.added);
}
}
class CountingSet<E> extends HashSet<E> {
int added = 0;
@Override
public boolean add(E e) {
added++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
added += c.size();
return super.addAll(c);
}
}Predict the output
addAll adds 3 to the counter, then super.addAll (inherited from AbstractCollection) calls add() for each element. That add() is our override, so every element is counted twice, giving 6. The subclass depends on an undocumented detail of its parent: the fragile base class problem, fixed by holding a Set instead of extending one.
What are sealed classes, and how do they change polymorphism in Java?
A sealed class or interface lists exactly which classes may extend it (final since Java 17). With pattern matching in switch (final since Java 21, JEP 441), the compiler checks that a switch covers every subtype.
public class Main {
sealed interface Shape permits Circle, Rect {}
record Circle(double r) implements Shape {}
record Rect(double w, double h) implements Shape {}
static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.r() * c.r();
case Rect r -> r.w() * r.h();
};
}
public static void main(String[] args) {
System.out.printf("%.2f %.2f%n", area(new Circle(1)), area(new Rect(2, 3)));
}
}It prints 3.14 6.00. There is no default; add a Triangle to permits and the switch stops compiling until it handles it.
How does runtime polymorphism work internally?
Through a method table, a vtable, in HotSpot and the major C++ compilers (neither standard requires it). Each class has a table of pointers to its overridable methods, an override takes the same slot as the method it replaces, and each object points to its class. A virtual call reads that slot and jumps.
- Interface calls in the JVM use a separate itable.
- HotSpot's JIT inlines a call site that has seen one class, with a guard that deoptimizes if another appears.
- Python looks methods up by name along the MRO at runtime.
What is the Law of Demeter?
A method should call methods only on itself, its fields, its parameters and objects it creates, not on objects those calls return.
// Violates: the caller knows Order, Customer, Address and their structure
String city = order.getCustomer().getAddress().getCity();
// Better: ask the object that owns the knowledge
String city = order.shippingCity();
The chain couples the caller to three classes. Fluent APIs such as stream().filter().map() are not violations, because each call returns the same kind of object.
How would you approach an object-oriented design question such as "design a parking lot"?
Clarify requirements, turn the nouns into classes and the verbs into methods, then walk one use case through the objects. The process and the trade-offs are graded, not the diagram.
- Ask: floors, vehicle and spot sizes, payment.
- Classes:
ParkingLot,Floor,Spot,Vehicle,Ticket,PricingPolicy. - Behavior:
park(vehicle)returns aTicket;unpark(ticket)frees the spot and returns the fee.
interface SpotFinder { Optional<Spot> find(List<Floor> floors, Vehicle v); }
interface PricingPolicy { long feeInPaise(Ticket t, Instant exit); }
class ParkingLot {
private final List<Floor> floors;
private final SpotFinder finder;
private final PricingPolicy pricing;
Ticket park(Vehicle v) { ... }
long unpark(Ticket t) { ... }
}
Mention concurrency (two cars must not claim one spot) and keep money in integer units, not double.
In Python, what happens to __hash__ when you define __eq__?
Defining __eq__ without __hash__ sets __hash__ to None, so instances become unhashable and cannot be set members or dict keys. That protects the rule that equal objects have equal hashes (see the __hash__ docs).
from dataclasses import dataclass
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)
print(Point(1, 2) == Point(1, 2))
try:
{Point(1, 2)}
except TypeError as e:
print(e)
@dataclass(frozen=True)
class FrozenPoint:
x: int
y: int
print(len({FrozenPoint(1, 2), FrozenPoint(1, 2)}))It prints True, then unhashable type: 'Point', then 1. Define __hash__ from the same fields (return hash((self.x, self.y))) or use @dataclass(frozen=True), which generates both.
Preparing for the interview
How should I prepare for an OOPs interview?
What OOPs questions are asked to freshers compared with experienced candidates?
static, access modifiers, overloading versus overriding, and output questions. Experienced candidates are asked why: when to choose composition, how equals and hashCode interact, how to make a class immutable, which pattern fits a problem, and how to design a small system.Which language should I use to answer OOPs questions?
abstract, @Override. In Python, be ready to explain the differences: no enforced private members, multiple inheritance with the MRO, and duck typing instead of declared interfaces.