Introduction
Landing your first tech job as a Computer Science fresher can feel overwhelming, especially when it comes to Object-Oriented Programming (OOP) interviews. Whether you’re preparing for positions at startups, tech giants like Google and Microsoft, or service-based companies, mastering OOP concepts is crucial for success.
This comprehensive guide covers the most frequently asked OOP interview questions, complete with detailed answers, code examples, and real-world scenarios that will help you ace your technical interviews. We’ve structured this guide based on actual interview patterns from top tech companies, ensuring you’re prepared for what recruiters really ask.
What You’ll Learn:
- Core OOP concepts explained simply
- Detailed answers with code examples in Java
- Real-world application scenarios
- Common interview pitfalls to avoid
- Expert tips from industry professionals
Understanding Object-Oriented Programming (OOP)
Before diving into specific questions, let’s understand what OOP is and why it matters in modern software development.
Object-Oriented Programming is a programming paradigm based on the concept of “objects” that contain data and code. OOP makes code more modular, reusable, and easier to maintain—skills that every employer looks for in fresh graduates.
Section 1: Fundamental OOP Interview Questions
Question 1: What Are the Four Pillars of OOP? (Most Asked!)
This is arguably the most common OOP interview question for freshers. Understanding these four pillars is essential for any programming interview.
The Complete Answer:
The four fundamental pillars of Object-Oriented Programming are:
1. Encapsulation
Encapsulation is the practice of bundling data (attributes) and methods that operate on that data within a single unit (class), while hiding internal implementation details from the outside world. This is achieved through access modifiers like private, protected, and public.
Real-world example: Think of a car’s engine. You can start the car without needing to understand the complex internal mechanics of how the engine works.
Code Example:
class BankAccount {
private double balance; // Hidden from outside
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public double getBalance() {
return balance;
}
}
2. Abstraction
Abstraction means hiding complex implementation details and showing only the necessary features of an object. It focuses on “what” an object does rather than “how” it does it.
Real-world example: When you drive a car, you use the steering wheel and pedals without needing to understand the internal transmission mechanics.
3. Inheritance
Inheritance is a mechanism where a new class (child class) inherits properties and behaviors from an existing class (parent class). This promotes code reusability and establishes hierarchical relationships.
Real-world example: A hierarchy like Vehicle → Car → SportsCar, where each level inherits properties from its parent.
4. Polymorphism
Polymorphism allows objects to take multiple forms. It enables methods to behave differently based on the object calling them. There are two types: compile-time polymorphism (method overloading) and runtime polymorphism (method overriding).
Real-world example: Different animals making different sounds when a makeSound() method is called.
Interview Tip: Always prepare specific code examples for each pillar. Interviewers often ask you to demonstrate these concepts with working code.
Question 2: What Is the Difference Between a Class and an Object?
This fundamental question tests your basic understanding of OOP concepts.
Detailed Answer:
A class is a blueprint or template that defines the structure and behavior (attributes and methods) that objects of that type will have. It’s a logical entity that doesn’t occupy memory until instantiated.
An object is an instance of a class. It’s a physical entity that occupies memory and has actual values for the attributes defined in the class.
Practical Example:
// Class Definition (Blueprint)
class Car {
String brand;
String color;
int speed;
void accelerate() {
speed += 10;
}
void brake() {
speed -= 10;
}
}
// Object Creation (Instance)
Car myCar = new Car(); // First object
myCar.brand = "Toyota";
myCar.color = "Red";
Car yourCar = new Car(); // Second object
yourCar.brand = "Honda";
yourCar.color = "Blue";
Key Differences:
| Aspect | Class | Object |
|---|---|---|
| Nature | Template/Blueprint | Instance/Realization |
| Memory | No memory until instantiated | Occupies memory |
| Declaration | Declared once | Multiple objects can be created |
| Example | Car (concept) | myCar, yourCar (actual cars) |
Interview Scenario: Interviewers might follow up with “How many objects can you create from one class?” (Answer: As many as memory allows—theoretically unlimited).
Question 3: Explain Encapsulation and Its Importance in OOP
Comprehensive Answer:
Encapsulation is one of the core principles of OOP that involves wrapping data and the methods that manipulate that data within a single unit (class) while restricting direct access to some of the object’s components.
Why Encapsulation Matters:
- Data Hiding: Protects object integrity by preventing unauthorized access and modification
- Flexibility: Internal implementation can change without affecting external code that uses the class
- Maintainability: Changes are localized to the class, making code easier to maintain
- Validation: Allows data validation through controlled access methods (getters/setters)
- Security: Sensitive information can be protected from external manipulation
Real-World Banking Example:
class BankAccount {
// Private variables - cannot be accessed directly
private String accountNumber;
private double balance;
private String customerName;
// Constructor
public BankAccount(String accNum, String name) {
this.accountNumber = accNum;
this.customerName = name;
this.balance = 0.0;
}
// Public method with validation
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("Deposited: $" + amount);
} else {
System.out.println("Invalid amount");
}
}
// Public method with business logic
public boolean withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("Withdrawn: $" + amount);
return true;
} else {
System.out.println("Insufficient funds or invalid amount");
return false;
}
}
// Getter method - controlled access
public double getBalance() {
return balance;
}
// No setter for balance - can only change through deposit/withdraw
}
// Usage
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount("12345", "John Doe");
// This works - using public methods
account.deposit(1000);
account.withdraw(500);
System.out.println("Balance: $" + account.getBalance());
// This won't work - balance is private
// account.balance = 1000000; // Compilation Error!
}
}
Interview Tip: Be ready to explain how encapsulation is implemented in your preferred programming language using access modifiers (private, protected, public, default).
Question 4: What’s the Difference Between Abstract Classes and Interfaces?
This is a critical interview question that separates candidates who truly understand OOP from those who memorize definitions.
Detailed Comparison:
Abstract Classes
An abstract class is a class that cannot be instantiated and may contain both abstract methods (without implementation) and concrete methods (with implementation).
Characteristics:
- Can have both abstract and non-abstract methods
- Can have instance variables with any access modifier
- Can have constructors
- Supports single inheritance only (a class can extend only one abstract class)
- Used when classes share common base functionality with some shared implementation
- Can have static methods and variables
Interfaces
An interface is a contract that defines a set of methods that implementing classes must provide. It represents capabilities or behaviors.
Characteristics:
- All methods are abstract by default (Java 8+ allows default and static methods)
- Variables are public, static, and final by default (constants only)
- Cannot have constructors
- Supports multiple inheritance (a class can implement multiple interfaces)
- Used to define contracts that unrelated classes can implement
- Represents “can-do” relationships
Practical Code Example:
// Abstract Class Example
abstract class Animal {
String name;
int age;
// Constructor in abstract class
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// Abstract method - must be implemented by subclass
abstract void makeSound();
// Concrete method - shared implementation
void sleep() {
System.out.println(name + " is sleeping...");
}
void eat() {
System.out.println(name + " is eating...");
}
}
// Interface Example
interface Flyable {
// Abstract method by default
void fly();
void land();
}
interface Swimmable {
void swim();
}
// Class implementing interface and extending abstract class
class Duck extends Animal implements Flyable, Swimmable {
public Duck(String name, int age) {
super(name, age);
}
// Must implement abstract method from Animal
@Override
void makeSound() {
System.out.println("Quack! Quack!");
}
// Must implement all methods from Flyable
@Override
public void fly() {
System.out.println(name + " is flying!");
}
@Override
public void land() {
System.out.println(name + " is landing!");
}
// Must implement method from Swimmable
@Override
public void swim() {
System.out.println(name + " is swimming!");
}
}
// Usage
public class Main {
public static void main(String[] args) {
Duck myDuck = new Duck("Donald", 2);
myDuck.makeSound(); // From Animal
myDuck.sleep(); // From Animal
myDuck.fly(); // From Flyable
myDuck.swim(); // From Swimmable
}
}
Decision Matrix – When to Use What:
| Use Abstract Class When | Use Interface When |
|---|---|
| Classes share common code | Defining capabilities for unrelated classes |
| You need constructors | Multiple inheritance is needed |
| You have state (instance variables) | You’re defining a contract/protocol |
| Related classes in a hierarchy | Implementing multiple behaviors |
| Example: Animal → Dog, Cat | Example: Flyable, Swimmable, Runnable |
Interview Follow-up Questions:
- “Can an abstract class have no abstract methods?” (Yes, but then it should probably be concrete)
- “Can you instantiate an abstract class?” (No, but you can create anonymous classes)
- “Can interfaces have constructors?” (No, because interfaces cannot be instantiated)
Question 5: Explain Method Overloading vs Method Overriding
This question tests your understanding of polymorphism—one of the most important OOP concepts.
Method Overloading (Compile-Time Polymorphism)
Method overloading occurs when multiple methods in the same class have the same name but different parameters (different number, type, or order of parameters).
Key Points:
- Same method name, different signatures
- Happens within the same class
- Resolved at compile-time (static binding)
- Return type can be different but isn’t sufficient alone for overloading
- Also called static polymorphism or early binding
Detailed Example:
class Calculator {
// Overloaded method - different number of parameters
int add(int a, int b) {
return a + b;
}
// Overloaded method - three parameters
int add(int a, int b, int c) {
return a + b + c;
}
// Overloaded method - different type of parameters
double add(double a, double b) {
return a + b;
}
// Overloaded method - different order of parameters
String add(String a, int b) {
return a + b;
}
String add(int a, String b) {
return a + b;
}
}
// Usage
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // Calls first method: 15
System.out.println(calc.add(5, 10, 15)); // Calls second method: 30
System.out.println(calc.add(5.5, 10.5)); // Calls third method: 16.0
System.out.println(calc.add("Sum: ", 100)); // Calls fourth method: Sum: 100
Method Overriding (Runtime Polymorphism)
Method overriding occurs when a subclass provides a specific implementation for a method that is already defined in its parent class.
Key Points:
- Same method name and signature
- Requires inheritance (parent-child relationship)
- Resolved at runtime (dynamic binding)
- Must have the same return type (or covariant return type)
- Access modifier cannot be more restrictive than parent
- Also called dynamic polymorphism or late binding
Comprehensive Example:
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
void eat() {
System.out.println("Animal eats");
}
}
class Dog extends Animal {
// Overriding makeSound method
@Override
void makeSound() {
System.out.println("Dog barks: Woof! Woof!");
}
// Overriding eat method
@Override
void eat() {
System.out.println("Dog eats dog food");
}
}
class Cat extends Animal {
// Overriding makeSound method
@Override
void makeSound() {
System.out.println("Cat meows: Meow! Meow!");
}
// Overriding eat method
@Override
void eat() {
System.out.println("Cat eats cat food");
}
}
// Demonstrating Runtime Polymorphism
public class Main {
public static void main(String[] args) {
Animal myAnimal; // Reference variable
myAnimal = new Dog();
myAnimal.makeSound(); // Output: Dog barks: Woof! Woof!
myAnimal.eat(); // Output: Dog eats dog food
myAnimal = new Cat();
myAnimal.makeSound(); // Output: Cat meows: Meow! Meow!
myAnimal.eat(); // Output: Cat eats cat food
}
}
Side-by-Side Comparison:
| Feature | Overloading | Overriding |
|---|---|---|
| Definition | Same name, different parameters | Same name, same parameters |
| Scope | Within same class | Across parent-child classes |
| Polymorphism Type | Compile-time (static) | Runtime (dynamic) |
| Binding | Early binding | Late binding |
| Inheritance | Not required | Required |
| Performance | Slightly faster | Slightly slower |
| Return Type | Can be different | Must be same (or covariant) |
| @Override Annotation | Not used | Recommended |
Rules for Overriding (Important for Interviews):
- Method signature must be identical
- Access modifier cannot be more restrictive
- Cannot override static methods (they’re hidden, not overridden)
- Cannot override final methods
- Cannot override private methods (not visible to subclass)
- Can throw fewer or narrower checked exceptions
Interview Tip: Always use the @Override annotation when overriding—it helps catch errors at compile-time and makes code more readable.
Question 6: What Is a Constructor? Explain Different Types
Constructors are fundamental to object creation and are frequently tested in fresher interviews.
What Is a Constructor?
A constructor is a special method that is automatically called when an object of a class is created. It initializes the object’s state and allocates memory.
Key Characteristics:
- Has the same name as the class
- No return type (not even void)
- Called automatically when object is created using ‘new’ keyword
- Can be overloaded
- Cannot be inherited (but can be called from child class)
- Cannot be abstract, static, or final
Types of Constructors:
1. Default Constructor
Provided automatically by the compiler if no constructor is defined. It initializes instance variables to default values.
class Student {
String name;
int age;
// No constructor defined
// Compiler provides: Student() { }
}
Student s = new Student(); // Uses default constructor
// name = null, age = 0
2. No-Argument Constructor
Explicitly defined by the programmer with no parameters.
class Student {
String name;
int rollNumber;
// No-argument constructor
Student() {
name = "Unknown";
rollNumber = 0;
System.out.println("Student object created");
}
}
Student s1 = new Student(); // name = "Unknown", rollNumber = 0
3. Parameterized Constructor
Accepts parameters to initialize object with specific values.
class Student {
String name;
int rollNumber;
String course;
// Parameterized constructor
Student(String n, int roll, String c) {
name = n;
rollNumber = roll;
course = c;
}
// Overloaded constructor
Student(String n, int roll) {
name = n;
rollNumber = roll;
course = "Not Assigned";
}
}
Student s1 = new Student("Alice", 101, "CS");
Student s2 = new Student("Bob", 102);
4. Copy Constructor
Creates a new object as a copy of an existing object.
class Student {
String name;
int rollNumber;
// Parameterized constructor
Student(String n, int roll) {
name = n;
rollNumber = roll;
}
// Copy constructor
Student(Student other) {
this.name = other.name;
this.rollNumber = other.rollNumber;
System.out.println("Copy constructor called");
}
}
Student original = new Student("Alice", 101);
Student copy = new Student(original); // Creates a copy
Constructor Chaining Example:
class Employee {
String name;
int id;
String department;
double salary;
// Constructor 1
Employee() {
this("Unknown", 0); // Calls constructor 2
}
// Constructor 2
Employee(String name, int id) {
this(name, id, "Not Assigned"); // Calls constructor 3
}
// Constructor 3
Employee(String name, int id, String dept) {
this(name, id, dept, 0.0); // Calls constructor 4
}
// Constructor 4 - All parameters
Employee(String name, int id, String dept, double salary) {
this.name = name;
this.id = id;
this.department = dept;
this.salary = salary;
}
}
Using super() for Parent Constructor:
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
int rollNumber;
String course;
Student(String name, int age, int roll, String course) {
super(name, age); // Calls parent constructor - MUST be first line
this.rollNumber = roll;
this.course = course;
}
}
Important Constructor Rules:
- If you define any constructor, the default constructor is NOT provided
this()andsuper()must be the first statement in constructor- You cannot use both
this()andsuper()in the same constructor - Constructors are not inherited but can be invoked from child class
Interview Follow-up Questions:
- “Can constructors be private?” (Yes—used in Singleton pattern)
- “Can we have a constructor in abstract class?” (Yes, to initialize common fields)
- “What happens if constructor throws an exception?” (Object creation fails, memory is released)
Question 7: Explain Inheritance and Its Types
Inheritance is a cornerstone of OOP that enables code reusability and establishes relationships between classes.
What Is Inheritance?
Inheritance is a mechanism by which one class (child/derived/subclass) acquires the properties and behaviors of another class (parent/base/superclass). It represents an “IS-A” relationship.
Benefits of Inheritance:
- Code reusability—avoid writing the same code multiple times
- Method overriding—enables runtime polymorphism
- Establishes hierarchical relationships
- Easier maintenance and updates
- Promotes extensibility
Types of Inheritance:
1. Single Inheritance
One child class inherits from one parent class—the simplest form of inheritance.
class Vehicle {
String brand;
int year;
void start() {
System.out.println("Vehicle is starting...");
}
}
class Car extends Vehicle {
int numberOfDoors;
void honk() {
System.out.println("Car is honking!");
}
}
// Usage
Car myCar = new Car();
myCar.brand = "Toyota"; // Inherited from Vehicle
myCar.start(); // Inherited method
myCar.honk(); // Own method
Hierarchy: Vehicle → Car
2. Multilevel Inheritance
A chain of inheritance where a class inherits from a child class, creating multiple levels.
class Vehicle {
void start() {
System.out.println("Vehicle starting...");
}
}
class Car extends Vehicle {
void drive() {
System.out.println("Car is driving...");
}
}
class SportsCar extends Car {
void turboBoost() {
System.out.println("Turbo boost activated!");
}
}
// Usage
SportsCar ferrari = new SportsCar();
ferrari.start(); // From Vehicle
ferrari.drive(); // From Car
ferrari.turboBoost(); // From SportsCar
Hierarchy: Vehicle → Car → SportsCar
3. Hierarchical Inheritance
Multiple child classes inherit from a single parent class.
class Animal {
void eat() {
System.out.println("Animal is eating...");
}
void sleep() {
System.out.println("Animal is sleeping...");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Dog barks: Woof!");
}
}
class Cat extends Animal {
void meow() {
System.out.println("Cat meows: Meow!");
}
}
class Bird extends Animal {
void chirp() {
System.out.println("Bird chirps: Tweet!");
}
}
// Each child inherits from Animal but not from each other
Hierarchy:
Animal
/ | \
Dog Cat Bird
4. Multiple Inheritance (Through Interfaces)
A class implements multiple interfaces to inherit behaviors from multiple sources. Java doesn’t support multiple inheritance with classes to avoid the “diamond problem.”
interface Walkable {
void walk();
}
interface Swimmable {
void swim();
}
interface Flyable {
void fly();
}
// Class implementing multiple interfaces
class Duck implements Walkable, Swimmable, Flyable {
@Override
public void walk() {
System.out.println("Duck is walking...");
}
@Override
public void swim() {
System.out.println("Duck is swimming...");
}
@Override
public void fly() {
System.out.println("Duck is flying...");
}
}
// Usage
Duck myDuck = new Duck();
myDuck.walk();
myDuck.swim();
myDuck.fly();
5. Hybrid Inheritance
A combination of two or more types of inheritance, typically achieved through interfaces in Java.
class Animal {
void breathe() {
System.out.println("Breathing...");
}
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class Bird extends Animal implements CanFly {
@Override
public void fly() {
System.out.println("Bird is flying...");
}
}
class Duck extends Bird implements CanSwim {
@Override
public void swim() {
System.out.println("Duck is swimming...");
}
}
The Diamond Problem (Why Java Restricts Multiple Inheritance):
A
/ \
B C
\ /
D
If both B and C override a method from A, which version should D inherit? This ambiguity is why Java doesn’t allow multiple inheritance with classes.
Inheritance Best Practices:
- Use inheritance for “IS-A” relationships only
- Prefer composition over inheritance when appropriate
- Keep inheritance hierarchies shallow (max 3-4 levels)
- Use
finalkeyword to prevent unwanted inheritance - Follow Liskov Substitution Principle—child should be usable wherever parent is expected
Interview Tip: Be ready to draw inheritance diagrams and explain why certain designs are better than others.
Question 8: What Is the “super” Keyword in Java?
The super keyword is crucial for working with inheritance and is frequently tested in interviews.
Definition:
The super keyword is a reference variable that refers to the immediate parent class object. It’s used to access parent class members when there’s ambiguity or to explicitly invoke parent class constructors.
Three Main Uses of super:
1. Accessing Parent Class Variables
When a child class has a variable with the same name as the parent class, super helps distinguish between them.
class Parent {
int value = 100;
String name = "Parent Class";
}
class Child extends Parent {
int value = 200;
String name = "Child Class";
void display() {
System.out.println("Child value: " + value); // 200
System.out.println("Parent value: " + super.value); // 100
System.out.println("Child name: " + name); // Child Class
System.out.println("Parent name: " + super.name); // Parent Class
}
}
// Usage
Child obj = new Child();
obj.display();
2. Calling Parent Class Methods
When a child class overrides a parent method, you can still access the parent’s version using super.
class Vehicle {
void start() {
System.out.println("Vehicle is starting with a key");
}
void displayInfo() {
System.out.println("This is a vehicle");
}
}
class Car extends Vehicle {
@Override
void start() {
super.start(); // Calls parent's start() first
System.out.println("Car engine started with push button");
}
@Override
void displayInfo() {
super.displayInfo(); // Calls parent's displayInfo()
System.out.println("This is a car with 4 wheels");
}
}
// Usage
Car myCar = new Car();
myCar.start();
// Output:
// Vehicle is starting with a key
// Car engine started with push button
myCar.displayInfo();
// Output:
// This is a vehicle
// This is a car with 4 wheels
3. Invoking Parent Class Constructor
The most important use—calling the parent class constructor from the child class constructor. This must be the first statement.
class Person {
String name;
int age;
// Parent constructor
Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person constructor called");
}
Person() {
this("Unknown", 0);
System.out.println("Person no-arg constructor called");
}
}
class Student extends Person {
int rollNumber;
String course;
// Child constructor
Student(String name, int age, int roll, String course) {
super(name, age); // MUST be first line - calls Person(String, int)
this.rollNumber = roll;
this.course = course;
System.out.println("Student constructor called");
}
Student(int roll, String course) {
super(); // Calls Person() no-arg constructor
this.rollNumber = roll;
this.course = course;
}
}
// Usage
Student s1 = new Student("Alice", 20, 101, "Computer Science");
// Output:
// Person constructor called
// Student constructor called
Student s2 = new Student(102, "Mathematics");
// Output:
// Person constructor called
// Person no-arg constructor called
// Student constructor called
Complex Example Showing All Uses:
class BankAccount {
protected double balance;
protected String accountHolder;
BankAccount(String holder, double initialBalance) {
this.accountHolder = holder;
this.balance = initialBalance;
}
void displayBalance() {
System.out.println("Account Holder: " + accountHolder);
System.out.println("Balance: $" + balance);
}
void deposit(double amount) {
balance += amount;
System.out.println("Deposited: $" + amount);
}
}
class SavingsAccount extends BankAccount {
private double interestRate;
private double balance; // Shadows parent's balance
SavingsAccount(String holder, double initialBalance, double rate) {
super(holder, initialBalance); // Call parent constructor
this.interestRate = rate;
this.balance = 0; // Local balance
}
@Override
void displayBalance() {
super.displayBalance(); // Call parent's method
System.out.println("Interest Rate: " + interestRate + "%");
System.out.println("Local balance: $" + balance);
System.out.println("Parent balance: $" + super.balance);
}
void addInterest() {
double interest = super.balance * (interestRate / 100);
super.deposit(interest); // Call parent's deposit method
System.out.println("Interest added: $" + interest);
}
}
Important Rules About super:
super()must be the first statement in a constructor- You cannot use both
this()andsuper()in the same constructor - If you don’t explicitly call
super(), Java automatically calls the no-arg parent constructor supercan be used in instance methods, not in static methodssuper.superis not allowed—you can only access immediate parent
Interview Follow-ups:
- “What if parent class doesn’t have a no-arg constructor?” (You must explicitly call parameterized constructor with super())
- “Can we use super in static methods?” (No, static methods don’t have access to instance context)
- “What’s the difference between this and super?” (this = current object, super = parent object)
Question 9: Static vs Instance Variables—What’s the Difference?
Understanding the difference between static and instance variables is crucial for memory management and design decisions.
Detailed Explanation:
Instance Variables (Non-Static)
Instance variables belong to an object (instance of a class). Each object gets its own copy.
Characteristics:
- Declared inside class but outside methods
- Created when object is created (with
newkeyword) - Destroyed when object is garbage collected
- Stored in heap memory
- Accessed through object reference
- Each object has independent values
- Cannot be accessed without creating object
Static Variables (Class Variables)
Static variables belong to the class itself, not to any specific object. All instances share the same copy.
Characteristics:
- Declared with
statickeyword - Created when class is loaded (before any object creation)
- Destroyed when program ends
- Stored in method area (or metaspace in modern JVMs)
- Accessed through class name (or object reference)
- Shared among all instances
- Can be accessed without creating an object
Comprehensive Example:
class Student {
// Static variable - shared by all students
static String schoolName = "ABC High School";
static int studentCount = 0;
// Instance variables - unique to each student
String name;
int rollNumber;
double gpa;
// Constructor
Student(String name, int roll, double gpa) {
this.name = name;
this.rollNumber = roll;
this.gpa = gpa;
studentCount++; // Increment shared counter
}
// Instance method
void displayInfo() {
System.out.println("Name: " + name);
System.out.println("Roll: " + rollNumber);
System.out.println("GPA: " + gpa);
System.out.println("School: " + schoolName); // Accessing static variable
}
// Static method
static void displaySchoolInfo() {
System.out.println("School: " + schoolName);
System.out.println("Total Students: " + studentCount);
// System.out.println(name); // ERROR! Cannot access instance variable
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Accessing static variable without creating object
System.out.println(Student.schoolName); // ABC High School
// Creating objects
Student s1 = new Student("Alice", 101, 3.8);
Student s2 = new Student("Bob", 102, 3.6);
Student s3 = new Student("Charlie", 103, 3.9);
// Each student has unique instance variables
System.out.println(s1.name); // Alice
System.out.println(s2.name); // Bob
System.out.println(s3.name); // Charlie
// All students share the same static variable
System.out.println(Student.studentCount); // 3
// Changing static variable affects all instances
Student.schoolName = "XYZ High School";
s1.displayInfo(); // Will show XYZ High School
s2.displayInfo(); // Will show XYZ High School
// Calling static method
Student.displaySchoolInfo();
}
}
Memory Representation:
Method Area (Class Memory):
├── Student.schoolName = “ABC High School”
└── Student.studentCount = 3
Heap Memory (Object Memory):
├── s1: {name: “Alice”, rollNumber: 101, gpa: 3.8}
├── s2: {name: “Bob”, rollNumber: 102, gpa: 3.6}
└── s3: {name: “Charlie”, rollNumber: 103, gpa: 3.9}
Side-by-Side Comparison:
| Feature | Static Variable | Instance Variable |
|---|---|---|
| Keyword | Declared with static |
No special keyword |
| Memory | Method area | Heap |
| Lifetime | From class loading to program end | From object creation to garbage collection |
| Copies | One copy per class | One copy per object |
| Access | Class.variable or object.variable | object.variable only |
| Used for | Shared data, constants, counters | Object-specific data |
| Example | Math.PI, Integer.MAX_VALUE | student.name, car.color |
Real-World Use Cases:
Static Variables:
class DatabaseConnection {
static String serverURL = "jdbc:mysql://localhost:3306/mydb";
static int maxConnections = 100;
static int activeConnections = 0;
}
class MathConstants {
static final double PI = 3.14159;
static final double E = 2.71828;
}
class GameStats {
static int totalGamesPlayed = 0;
static int totalPlayers = 0;
}
Instance Variables:
class Employee {
String name; // Each employee has unique name
int employeeId; // Each has unique ID
double salary; // Each has different salary
String department; // Can be in different departments
}
class BankAccount {
String accountNumber; // Unique per account
double balance; // Different for each account
String ownerName; // Different owner
}
Important Interview Points:
- Static variables are initialized when class is loaded, instance variables when object is created
- Static methods can only access static variables directly
- Instance methods can access both static and instance variables
- Static variables are often used with
finalfor constants - Too many static variables can lead to memory issues (they’re never garbage collected until program ends)
Interview Follow-ups:
- “Can we override static methods?” (No, they’re hidden not overridden)
- “What happens if we change a static variable from one object?” (Changes for all objects)
- “Can static variables be serialized?” (No, they’re skipped during serialization)
Question 10: Composition vs Aggregation—Understanding Relationships
Both represent “HAS-A” relationships but with different lifecycles and ownership semantics.
Composition (Strong “HAS-A” Relationship)
Composition represents a strong ownership where the child cannot exist independently of the parent. The lifecycle of the contained object is managed by the container.
Key Characteristics:
- Strong relationship—”part-of” semantics
- Child cannot exist without parent
- When parent is destroyed, child is destroyed
- Parent has exclusive ownership
- Typically implemented with private members created in constructor
Real-World Examples:
- Car HAS-A Engine (engine cannot exist without the car in our model)
- House HAS-A Room (rooms are part of the house)
- Human HAS-A Heart (heart is part of human)
- Book HAS-A Pages (pages belong to the book)
Code Example:
// Composition - Engine is part of Car
class Engine {
private String type;
private int horsepower;
Engine(String type, int hp) {
this.type = type;
this.horsepower = hp;
System.out.println("Engine created: " + type);
}
void start() {
System.out.println("Engine starting...");
}
}
class Car {
private String brand;
private Engine engine; // Composition - Car HAS-A Engine
// Engine is created inside Car constructor
Car(String brand, String engineType, int hp) {
this.brand = brand;
this.engine = new Engine(engineType, hp); // Car creates and owns Engine
System.out.println("Car created: " + brand);
}
void start() {
System.out.println("Starting " + brand);
engine.start();
}
// When Car is destroyed, Engine is also destroyed
}
// Usage
public class Main {
public static void main(String[] args) {
Car myCar = new Car("Toyota", "V6", 300);
myCar.start();
// When myCar goes out of scope, both Car and Engine are garbage collected
// Engine cannot exist independently
}
}
Aggregation (Weak “HAS-A” Relationship)
Aggregation represents a weaker relationship where the child can exist independently of the parent. The contained object has its own lifecycle.
Key Characteristics:
- Weak relationship—”has-a” semantics
- Child can exist independently
- When parent is destroyed, child survives
- Shared ownership possible
- Typically implemented by passing objects through constructor or setter
Real-World Examples:
- Department HAS Teachers (teachers can exist without department)
- Library HAS Books (books exist independently)
- Team HAS Players (players can switch teams)
- Playlist HAS Songs (songs exist independently)
Code Example:
// Aggregation - Teacher exists independently
class Teacher {
private String name;
private String subject;
Teacher(String name, String subject) {
this.name = name;
this.subject = subject;
System.out.println("Teacher created: " + name);
}
void teach() {
System.out.println(name + " is teaching " + subject);
}
String getName() {
return name;
}
}
class Department {
private String deptName;
private List<Teacher> teachers; // Aggregation - Department HAS Teachers
// Teachers are passed from outside, not created by Department
Department(String name, List<Teacher> teacherList) {
this.deptName = name;
this.teachers = teacherList; // Department doesn't create Teachers
System.out.println("Department created: " + name);
}
void displayTeachers() {
System.out.println("Teachers in " + deptName + ":");
for (Teacher t : teachers) {
System.out.println("- " + t.getName());
}
}
// When Department is destroyed, Teachers still exist
}
// Usage
public class Main {
public static void main(String[] args) {
// Teachers created independently
Teacher t1 = new Teacher("Dr. Smith", "Mathematics");
Teacher t2 = new Teacher("Dr. Jones", "Physics");
Teacher t3 = new Teacher("Dr. Brown", "Chemistry");
// Department uses existing teachers
List<Teacher> scienceTeachers = Arrays.asList(t1, t2, t3);
Department scienceDept = new Department("Science", scienceTeachers);
scienceDept.displayTeachers();
// If scienceDept is destroyed, teachers still exist
// Teachers can be reassigned to other departments
scienceDept = null;
t1.teach(); // Teacher still exists and works!
}
}
Detailed Comparison:
| Aspect | Composition | Aggregation |
|---|---|---|
| Relationship Type | Strong (part-of) | Weak (has-a) |
| Child Independence | Cannot exist without parent | Can exist independently |
| Lifecycle | Managed by parent | Independent |
| Ownership | Exclusive | Can be shared |
| UML Symbol | Filled diamond | Empty diamond |
| Deletion Impact | Child deleted with parent | Child survives parent deletion |
| Example | Car-Engine | Department-Teacher |
Visual Representation:
Composition:
Car ◆—————— Engine
(Filled diamond = strong ownership)
If Car is destroyed → Engine is destroyed
Aggregation:
Department ◇—————— Teacher
(Empty diamond = weak relationship)
If Department is destroyed → Teacher survives
Complete Real-World Example:
// University System demonstrating both
class Address {
private String street;
private String city;
Address(String street, String city) {
this.street = street;
this.city = city;
}
}
class Student {
private String name;
private int rollNumber;
Student(String name, int roll) {
this.name = name;
this.rollNumber = roll;
}
String getName() { return name; }
}
// Composition example
class University {
private String name;
private Address address; // COMPOSITION - Address is part of University
University(String name, String street, String city) {
this.name = name;
this.address = new Address(street, city); // University creates Address
}
// If University is destroyed, its Address is meaningless
}
// Aggregation example
class Course {
private String courseName;
private List<Student> enrolledStudents; // AGGREGATION - Students exist independently
Course(String name, List<Student> students) {
this.courseName = name;
this.enrolledStudents = students; // Course uses existing Students
}
void displayStudents() {
System.out.println("Students enrolled in " + courseName + ":");
for (Student s : enrolledStudents) {
System.out.println("- " + s.getName());
}
}
// If Course is destroyed, Students continue to exist
}
Interview Decision Framework:
Use Composition when:
- Part cannot exist without whole
- Strong ownership is needed
- Lifecycle management should be centralized
- Example: Car-Engine, House-Room, Document-Page
Use Aggregation when:
- Parts can exist independently
- Shared ownership is possible
- Objects need to be reusable across containers
- Example: Team-Player, Department-Employee, Playlist-Song
Interview Tip: Draw UML diagrams to visualize relationships. Interviewers appreciate candidates who can explain design decisions visually.
Section 2: Scenario-Based Interview Questions
Question 11: Design a Parking Lot System
This is a classic system design question that tests multiple OOP concepts simultaneously.
Approach Strategy: Before coding, follow these steps:
- Clarify requirements
- Identify main entities (classes)
- Define relationships
- Outline key methods
- Consider edge cases
Requirements Analysis:
- Parking lot has multiple levels/floors
- Different vehicle types (Car, Motorcycle, Truck)
- Different spot sizes
- Track availability
- Entry and exit system
- Pricing calculation
Object-Oriented Design:
// Enum for vehicle types
enum VehicleType {
MOTORCYCLE, CAR, TRUCK
}
// Enum for spot status
enum SpotStatus {
AVAILABLE, OCCUPIED, RESERVED
}
// Base Vehicle class
abstract class Vehicle {
protected String licensePlate;
protected VehicleType type;
protected String color;
Vehicle(String plate, VehicleType type, String color) {
this.licensePlate = plate;
this.type = type;
this.color = color;
}
VehicleType getType() {
return type;
}
String getLicensePlate() {
return licensePlate;
}
}
// Concrete vehicle classes
class Motorcycle extends Vehicle {
Motorcycle(String plate, String color) {
super(plate, VehicleType.MOTORCYCLE, color);
}
}
class Car extends Vehicle {
Car(String plate, String color) {
super(plate, VehicleType.CAR, color);
}
}
class Truck extends Vehicle {
Truck(String plate, String color) {
super(plate, VehicleType.TRUCK, color);
}
}
// Parking spot class
class ParkingSpot {
private int spotNumber;
private VehicleType spotType;
private SpotStatus status;
private Vehicle parkedVehicle;
ParkingSpot(int number, VehicleType type) {
this.spotNumber = number;
this.spotType = type;
this.status = SpotStatus.AVAILABLE;
}
boolean isAvailable() {
return status == SpotStatus.AVAILABLE;
}
boolean canFitVehicle(Vehicle vehicle) {
// Motorcycle can fit in any spot
// Car needs car or truck spot
// Truck needs truck spot only
if (vehicle.getType() == VehicleType.MOTORCYCLE) {
return true;
}
if (vehicle.getType() == VehicleType.CAR) {
return spotType == VehicleType.CAR || spotType == VehicleType.TRUCK;
}
return vehicle.getType() == spotType;
}
boolean park(Vehicle vehicle) {
if (isAvailable() && canFitVehicle(vehicle)) {
this.parkedVehicle = vehicle;
this.status = SpotStatus.OCCUPIED;
return true;
}
return false;
}
void removeVehicle() {
this.parkedVehicle = null;
this.status = SpotStatus.AVAILABLE;
}
int getSpotNumber() {
return spotNumber;
}
}
// Level/Floor class
class Level {
private int floor;
private List<ParkingSpot> spots;
Level(int floor, int motorcycleSpots, int carSpots, int truckSpots) {
this.floor = floor;
this.spots = new ArrayList<>();
int spotNumber = floor * 100; // Spot numbers: 100, 101, 102...
// Add motorcycle spots
for (int i = 0; i < motorcycleSpots; i++) {
spots.add(new ParkingSpot(spotNumber++, VehicleType.MOTORCYCLE));
}
// Add car spots
for (int i = 0; i < carSpots; i++) {
spots.add(new ParkingSpot(spotNumber++, VehicleType.CAR));
}
// Add truck spots
for (int i = 0; i < truckSpots; i++) {
spots.add(new ParkingSpot(spotNumber++, VehicleType.TRUCK));
}
}
ParkingSpot findAvailableSpot(Vehicle vehicle) {
for (ParkingSpot spot : spots) {
if (spot.isAvailable() && spot.canFitVehicle(vehicle)) {
return spot;
}
}
return null;
}
int getAvailableSpots(VehicleType type) {
int count = 0;
for (ParkingSpot spot : spots) {
if (spot.isAvailable() && spot.canFitVehicle(
new Car("temp", "temp") {
@Override
VehicleType getType() { return type; }
})) {
count++;
}
}
return count;
}
}
// Main parking lot class
class ParkingLot {
private String name;
private List<Level> levels;
private Map<String, ParkingSpot> vehicleSpotMap; // licensePlate -> ParkingSpot
ParkingLot(String name, int numLevels) {
this.name = name;
this.levels = new ArrayList<>();
this.vehicleSpotMap = new HashMap<>();
// Create levels with different configurations
for (int i = 0; i < numLevels; i++) {
levels.add(new Level(i + 1, 10, 20, 5)); // 10 motorcycle, 20 car, 5 truck spots
}
}
boolean parkVehicle(Vehicle vehicle) {
// Check if vehicle is already parked
if (vehicleSpotMap.containsKey(vehicle.getLicensePlate())) {
System.out.println("Vehicle already parked!");
return false;
}
// Find available spot across all levels
for (Level level : levels) {
ParkingSpot spot = level.findAvailableSpot(vehicle);
if (spot != null && spot.park(vehicle)) {
vehicleSpotMap.put(vehicle.getLicensePlate(), spot);
System.out.println("Vehicle " + vehicle.getLicensePlate() +
" parked at spot " + spot.getSpotNumber());
return true;
}
}
System.out.println("No available spot for vehicle " + vehicle.getLicensePlate());
return false;
}
boolean unparkVehicle(String licensePlate) {
ParkingSpot spot = vehicleSpotMap.get(licensePlate);
if (spot != null) {
spot.removeVehicle();
vehicleSpotMap.remove(licensePlate);
System.out.println("Vehicle " + licensePlate + " removed from spot " +
spot.getSpotNumber());
return true;
}
System.out.println("Vehicle " + licensePlate + " not found!");
return false;
}
void displayAvailability() {
System.out.println("\n=== Parking Lot: " + name + " ===");
for (int i = 0; i < levels.size(); i++) {
Level level = levels.get(i);
System.out.println("Level " + (i + 1) + ":");
System.out.println(" Motorcycle spots: " + level.getAvailableSpots(VehicleType.MOTORCYCLE));
System.out.println(" Car spots: " + level.getAvailableSpots(VehicleType.CAR));
System.out.println(" Truck spots: " + level.getAvailableSpots(VehicleType.TRUCK));
}
}
}
// Demo usage
public class ParkingLotDemo {
public static void main(String[] args) {
ParkingLot lot = new ParkingLot("City Center Parking", 3);
// Create vehicles
Vehicle bike1 = new Motorcycle("BIKE-001", "Red");
Vehicle car1 = new Car("CAR-001", "Blue");
Vehicle car2 = new Car("CAR-002", "Black");
Vehicle truck1 = new Truck("TRUCK-001", "White");
// Display initial availability
lot.displayAvailability();
// Park vehicles
lot.parkVehicle(bike1);
lot.parkVehicle(car1);
lot.parkVehicle(car2);
lot.parkVehicle(truck1);
// Display after parking
lot.displayAvailability();
// Unpark a vehicle
lot.unparkVehicle("CAR-001");
// Display final state
lot.displayAvailability();
}
}
nterview Discussion Points:
1. Encapsulation:
- Private attributes with public methods
- Data hiding in ParkingSpot and Level classes
- Controlled access through getters/setters
2. Inheritance:
- Vehicle as base class
- Motorcycle, Car, Truck as derived classes
- Code reusability through inheritance
3. Abstraction:
- Vehicle is abstract (could be made explicitly abstract)
- Hides complexity of spot allocation
4. Polymorphism:
- Different vehicle types handled uniformly
- Method overriding in vehicle subclasses
5. Design Patterns:
- Singleton Pattern: ParkingLot could be a singleton
- Factory Pattern: Could add VehicleFactory
- Strategy Pattern: Different pricing strategies
Extension Questions:
- “How would you add pricing?” (Add PricingStrategy interface)
- “How to handle reserved spots?” (Add reservation system with timestamps)
- “How to optimize search?” (Use separate lists for each spot type)
Question 12: Design a Banking System
A comprehensive scenario that tests real-world application of OOP principles.
System Requirements:
- Multiple account types (Savings, Checking, Fixed Deposit)
- Different interest calculations
- Transaction history
- Customer management
- Account operations (deposit, withdraw, transfer)
Complete Design:
// Base Account class (Abstract)
abstract class Account {
protected String accountNumber;
protected double balance;
protected Customer owner;
protected List<Transaction> transactionHistory;
protected Date dateOpened;
Account(String accNum, Customer owner, double initialBalance) {
this.accountNumber = accNum;
this.owner = owner;
this.balance = initialBalance;
this.dateOpened = new Date();
this.transactionHistory = new ArrayList<>();
if (initialBalance > 0) {
transactionHistory.add(new Transaction("INITIAL_DEPOSIT", initialBalance, "Account opened"));
}
}
// Abstract method - different for each account type
abstract void calculateInterest();
abstract double getWithdrawalLimit();
abstract String getAccountType();
// Concrete methods - shared across all account types
boolean deposit(double amount) {
if (amount <= 0) {
System.out.println("Invalid deposit amount");
return false;
}
balance += amount;
transactionHistory.add(new Transaction("DEPOSIT", amount, "Deposit successful"));
System.out.println("Deposited: $" + amount + " | New Balance: $" + balance);
return true;
}
boolean withdraw(double amount) {
if (amount <= 0) {
System.out.println("Invalid withdrawal amount");
return false;
}
if (amount > getWithdrawalLimit()) {
System.out.println("Withdrawal limit exceeded");
return false;
}
if (amount > balance) {
System.out.println("Insufficient funds");
return false;
}
balance -= amount;
transactionHistory.add(new Transaction("WITHDRAWAL", amount, "Withdrawal successful"));
System.out.println("Withdrawn: $" + amount + " | New Balance: $" + balance);
return true;
}
double getBalance() {
return balance;
}
String getAccountNumber() {
return accountNumber;
}
void displayTransactionHistory() {
System.out.println("\n=== Transaction History for " + accountNumber + " ===");
for (Transaction t : transactionHistory) {
System.out.println(t);
}
}
void displayAccountInfo() {
System.out.println("\n=== Account Information ===");
System.out.println("Account Type: " + getAccountType());
System.out.println("Account Number: " + accountNumber);
System.out.println("Owner: " + owner.getName());
System.out.println("Balance: $" + balance);
System.out.println("Date Opened: " + dateOpened);
}
}
// Savings Account
class SavingsAccount extends Account {
private double interestRate;
private double minimumBalance;
private static final double WITHDRAWAL_LIMIT = 5000.0;
SavingsAccount(String accNum, Customer owner, double initialBalance, double rate) {
super(accNum, owner, initialBalance);
this.interestRate = rate;
this.minimumBalance = 1000.0;
}
@Override
void calculateInterest() {
if (balance >= minimumBalance) {
double interest = balance * (interestRate / 100);
balance += interest;
transactionHistory.add(new Transaction("INTEREST", interest, "Interest credited"));
System.out.println("Interest credited: $" + interest);
}
}
@Override
double getWithdrawalLimit() {
return WITHDRAWAL_LIMIT;
}
@Override
String getAccountType() {
return "Savings Account";
}
@Override
boolean withdraw(double amount) {
if (balance - amount < minimumBalance) {
System.out.println("Cannot withdraw - minimum balance requirement: $" + minimumBalance);
return false;
}
return super.withdraw(amount);
}
}
// Checking Account
class CheckingAccount extends Account {
private double overdraftLimit;
private static final double WITHDRAWAL_LIMIT = 10000.0;
CheckingAccount(String accNum, Customer owner, double initialBalance, double overdraft) {
super(accNum, owner, initialBalance);
this.overdraftLimit = overdraft;
}
@Override
void calculateInterest() {
// No interest for checking accounts
System.out.println("Checking accounts don't earn interest");
}
@Override
double getWithdrawalLimit() {
return WITHDRAWAL_LIMIT;
}
@Override
String getAccountType() {
return "Checking Account";
}
@Override
boolean withdraw(double amount) {
if (amount > balance + overdraftLimit) {
System.out.println("Overdraft limit exceeded");
return false;
}
balance -= amount;
transactionHistory.add(new Transaction("WITHDRAWAL", amount, "Withdrawal successful"));
if (balance < 0) {
System.out.println("Withdrawn: $" + amount + " | Balance: $" + balance + " (Overdraft used)");
} else {
System.out.println("Withdrawn: $" + amount + " | Balance: $" + balance);
}
return true;
}
}
// Fixed Deposit Account
class FixedDepositAccount extends Account {
private double interestRate;
private int termMonths;
private Date maturityDate;
FixedDepositAccount(String accNum, Customer owner, double amount, double rate, int months) {
super(accNum, owner, amount);
this.interestRate = rate;
this.termMonths = months;
calculateMaturityDate();
}
private void calculateMaturityDate() {
Calendar cal = Calendar.getInstance();
cal.setTime(dateOpened);
cal.add(Calendar.MONTH, termMonths);
maturityDate = cal.getTime();
}
@Override
void calculateInterest() {
// Compound interest for fixed deposit
double interest = balance * Math.pow(1 + (interestRate/100), termMonths/12.0) - balance;
balance += interest;
transactionHistory.add(new Transaction("INTEREST", interest, "Maturity interest credited"));
System.out.println("Maturity interest credited: $" + interest);
}
@Override
double getWithdrawalLimit() {
return 0; // No withdrawal before maturity
}
@Override
String getAccountType() {
return "Fixed Deposit Account";
}
@Override
boolean withdraw(double amount) {
Date today = new Date();
if (today.before(maturityDate)) {
System.out.println("Cannot withdraw before maturity date: " + maturityDate);
System.out.println("Premature withdrawal penalties apply. Contact bank.");
return false;
}
return super.withdraw(amount);
}
void displayMaturityInfo() {
System.out.println("Maturity Date: " + maturityDate);
System.out.println("Interest Rate: " + interestRate + "%");
System.out.println("Term: " + termMonths + " months");
}
}
// Transaction class
class Transaction {
private String type;
private double amount;
private Date timestamp;
private String description;
Transaction(String type, double amount, String desc) {
this.type = type;
this.amount = amount;
this.timestamp = new Date();
this.description = desc;
}
@Override
public String toString() {
return timestamp + " | " + type + " | $" + amount + " | " + description;
}
}
// Customer class
class Customer {
private String customerId;
private String name;
private String email;
private String phone;
private List<Account> accounts;
Customer(String id, String name, String email, String phone) {
this.customerId = id;
this.name = name;
this.email = email;
this.phone = phone;
this.accounts = new ArrayList<>();
}
void addAccount(Account account) {
accounts.add(account);
System.out.println("Account " + account.getAccountNumber() + " added for " + name);
}
String getName() {
return name;
}
String getCustomerId() {
return customerId;
}
void displayAllAccounts() {
System.out.println("\n=== Accounts for " + name + " ===");
for (Account acc : accounts) {
System.out.println(acc.getAccountType() + " - " + acc.getAccountNumber() +
" | Balance: $" + acc.getBalance());
}
}
}
// Bank class - manages everything
class Bank {
private String bankName;
private List<Customer> customers;
private List<Account> accounts;
private int accountCounter;
private int customerCounter;
Bank(String name) {
this.bankName = name;
this.customers = new ArrayList<>();
this.accounts = new ArrayList<>();
this.accountCounter = 1000;
this.customerCounter = 100;
}
Customer createCustomer(String name, String email, String phone) {
String custId = "CUST" + (customerCounter++);
Customer customer = new Customer(custId, name, email, phone);
customers.add(customer);
System.out.println("Customer created: " + custId + " - " + name);
return customer;
}
Account createSavingsAccount(Customer customer, double initialBalance, double interestRate) {
String accNum = "SAV" + (accountCounter++);
Account account = new SavingsAccount(accNum, customer, initialBalance, interestRate);
accounts.add(account);
customer.addAccount(account);
return account;
}
Account createCheckingAccount(Customer customer, double initialBalance, double overdraft) {
String accNum = "CHK" + (accountCounter++);
Account account = new CheckingAccount(accNum, customer, initialBalance, overdraft);
accounts.add(account);
customer.addAccount(account);
return account;
}
Account createFixedDepositAccount(Customer customer, double amount, double rate, int months) {
String accNum = "FD" + (accountCounter++);
Account account = new FixedDepositAccount(accNum, customer, amount, rate, months);
accounts.add(account);
customer.addAccount(account);
return account;
}
boolean transfer(Account from, Account to, double amount) {
System.out.println("\n=== Transfer Request ===");
System.out.println("From: " + from.getAccountNumber());
System.out.println("To: " + to.getAccountNumber());
System.out.println("Amount: $" + amount);
if (from.withdraw(amount)) {
to.deposit(amount);
System.out.println("Transfer successful!");
return true;
}
System.out.println("Transfer failed!");
return false;
}
void applyInterestToAll() {
System.out.println("\n=== Applying Interest to All Accounts ===");
for (Account acc : accounts) {
System.out.println("\nAccount: " + acc.getAccountNumber());
acc.calculateInterest();
}
}
}
// Demo
public class BankingSystemDemo {
public static void main(String[] args) {
// Create bank
Bank bank = new Bank("ABC Bank");
// Create customers
Customer john = bank.createCustomer("John Doe", "john@email.com", "123-456-7890");
Customer alice = bank.createCustomer("Alice Smith", "alice@email.com", "098-765-4321");
// Create accounts
Account johnSavings = bank.createSavingsAccount(john, 5000, 3.5);
Account johnChecking = bank.createCheckingAccount(john, 2000, 1000);
Account aliceSavings = bank.createSavingsAccount(alice, 10000, 3.5);
Account aliceFD = bank.createFixedDepositAccount(alice, 20000, 7.0, 12);
// Display all accounts
john.displayAllAccounts();
alice.displayAllAccounts();
// Perform transactions
johnSavings.deposit(1000);
johnSavings.withdraw(500);
johnChecking.withdraw(2500); // Uses overdraft
// Transfer money
bank.transfer(johnSavings, aliceSavings, 1000);
// Display transaction history
johnSavings.displayTransactionHistory();
// Apply interest
bank.applyInterestToAll();
// Display final account info
johnSavings.displayAccountInfo();
aliceFD.displayAccountInfo();
if (aliceFD instanceof FixedDepositAccount) {
((FixedDepositAccount) aliceFD).displayMaturityInfo();
}
}
}
OOP Principles Demonstrated:
1. Encapsulation:
- Private attributes (balance, accountNumber)
- Public methods for controlled access
- Data validation in methods
2. Abstraction:
- Account is abstract class
- Abstract method calculateInterest()
- Hides implementation complexity
3. Inheritance:
- SavingsAccount, CheckingAccount, FixedDepositAccount extend Account
- Code reuse through inheritance
- Specialized behavior in child classes
4. Polymorphism:
- Different interest calculations
- Overridden withdraw() methods
- Runtime polymorphism in action
Interview Discussion Points:
- SOLID Principles: Single Responsibility (each class has one purpose)
- Design Patterns: Template Method (calculateInterest), Strategy (could add pricing strategies)
- Error Handling: Validation in deposit/withdraw
- Scalability: Easy to add new account types
Section 3: Common Pitfalls & Expert Tips
Common Mistakes Freshers Make
1. Confusing Overloading with Overriding
// WRONG - This is overloading, not overriding
class Parent {
void show(int a) { }
}
class Child extends Parent {
void show(double a) { } // Different parameter type = overloading
}
// CORRECT - This is overriding
class Parent {
void show(int a) { }
}
class Child extends Parent {
@Override
void show(int a) { } // Same signature = overriding
}
2. Forgetting Constructors Aren’t Inherited
class Parent {
Parent(String name) {
System.out.println("Parent: " + name);
}
}
class Child extends Parent {
// MUST explicitly call parent constructor
Child() {
super("Default"); // Required!
}
}
3. Misunderstanding Static Context
class Example {
int instanceVar = 10;
static int staticVar = 20;
static void staticMethod() {
System.out.println(staticVar); // OK
// System.out.println(instanceVar); // ERROR! Can't access instance from static
}
}
4. Abstract Class vs Interface Confusion
// When to use abstract class:
abstract class Shape {
String color; // Common state
abstract double area();
void display() { // Common implementation
System.out.println("Shape with color: " + color);
}
}
// When to use interface:
interface Drawable {
void draw(); // Just behavior contract
}
interface Resizable {
void resize(double factor);
}
Interview Success Tips
1. Always Think Out Loud Show your thought process:
“Before I answer, let me clarify what you’re asking…”
“I’m thinking about two approaches here…”
“The trade-off between these options is…”
2. Use Real-World Examples: Connect concepts to everyday objects:
- Encapsulation → TV remote (you use it without knowing internals)
- Inheritance → Vehicle types (Car IS-A Vehicle)
- Polymorphism → Animal sounds (same method, different behaviors)
- Abstraction → Coffee machine (simple interface, complex internals)
3. Draw Diagrams: Visual representation helps:
Class Hierarchy:
Animal
├── Dog
└── Cat
Composition:
Car ◆──── Engine
Aggregation:
Department ◇──── Teacher
4. Ask Clarifying Questions For design problems:
- What are the functional requirements?
- Should I consider scalability?
- Are there any constraints I should know about?
- What’s the expected load/usage?
5. Mention Trade-offs Show mature thinking:
- Abstract classes give us shared implementation, but limit flexibility
- Composition is more flexible than inheritance, but requires more code
- This design is simple but might not scale well
Practice Strategy
Week 1-2: Core Concepts
- Master the four pillars
- Understand class relationships
- Practice writing small examples
Week 3-4: Intermediate Topics
- Design patterns (Singleton, Factory, Observer)
- SOLID principles basics
- Exception handling in OOP
Week 5-6: System Design
- Practice 2-3 design problems daily
- Draw UML diagrams
- Explain designs verbally
Daily Practice Routine:
- Review 2-3 concepts
- Code examples from scratch
- Explain concepts aloud
- Solve 1 design problem
Final Interview Tips
Day Before Interview:
- Review this guide once
- Practice explaining 2-3 concepts aloud
- Prepare 2-3 questions to ask interviewer
- Get good sleep!
During Interview:
- Stay calm and composed
- Ask clarifying questions
- Think before speaking
- Admit when you don’t know something
- Show enthusiasm for learning
Common Interview Flow:
- Explain basic OOP concepts (5-10 min)
- Write code for specific problems (15-20 min)
- Design a system (15-20 min)
- Discuss trade-offs and alternatives (5-10 min)
Good luck with your interviews! 🚀
Last Updated: January 2026
Subscribe for more interview tips and share this guide with friends.