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:

  1. Data Hiding: Protects object integrity by preventing unauthorized access and modification
  2. Flexibility: Internal implementation can change without affecting external code that uses the class
  3. Maintainability: Changes are localized to the class, making code easier to maintain
  4. Validation: Allows data validation through controlled access methods (getters/setters)
  5. 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):

  1. Method signature must be identical
  2. Access modifier cannot be more restrictive
  3. Cannot override static methods (they’re hidden, not overridden)
  4. Cannot override final methods
  5. Cannot override private methods (not visible to subclass)
  6. 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:

  1. If you define any constructor, the default constructor is NOT provided
  2. this() and super() must be the first statement in constructor
  3. You cannot use both this() and super() in the same constructor
  4. 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:

  1. Use inheritance for “IS-A” relationships only
  2. Prefer composition over inheritance when appropriate
  3. Keep inheritance hierarchies shallow (max 3-4 levels)
  4. Use final keyword to prevent unwanted inheritance
  5. 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:

  1. super() must be the first statement in a constructor
  2. You cannot use both this() and super() in the same constructor
  3. If you don’t explicitly call super(), Java automatically calls the no-arg parent constructor
  4. super can be used in instance methods, not in static methods
  5. super.super is 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 new keyword)
  • 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 static keyword
  • 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:

  1. Static variables are initialized when class is loaded, instance variables when object is created
  2. Static methods can only access static variables directly
  3. Instance methods can access both static and instance variables
  4. Static variables are often used with final for constants
  5. 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:

  1. Clarify requirements
  2. Identify main entities (classes)
  3. Define relationships
  4. Outline key methods
  5. 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:

  1. Review 2-3 concepts
  2. Code examples from scratch
  3. Explain concepts aloud
  4. 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:

  1. Explain basic OOP concepts (5-10 min)
  2. Write code for specific problems (15-20 min)
  3. Design a system (15-20 min)
  4. 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.

 

 

Author

Write A Comment