OOPs — Contents
OverviewWhy OOPs Is a Fresher Killer4 Pillars Reference
4 Pillars (Q1–Q10)Encapsulation Q1–Q2Inheritance Q3–Q5Polymorphism Q6–Q8Abstraction Q9–Q10
Core Concepts (Q11–Q20)Classes, Objects & ConstructorsKeywords & ModifiersJava Internals
Design (Q21–Q32)SOLID PrinciplesDesign PatternsAdvanced OOP
Jump to: Overview 4 Pillars Encapsulation Inheritance Polymorphism Abstraction Classes Keywords Java SOLID Patterns Advanced
🔷 Part 2 · Phase 3 of 6 · April 2025

Object-Oriented Programming
32 Real Interview Questions

Every OOPs question you will face — fully answered with real Java code, analogy-based explanations, SOLID principles, design patterns, and all the tricky differentiators interviewers love to test.

4 PillarsEncapsulationInheritancePolymorphismAbstractionSOLIDDesign PatternsAbstract vs InterfaceOverloading vs OverridingJava Internals
✎ The Tech Intel⏰ ~40 min read📋 32 Questions · All Answered💻 Full Java Code

OOPs is the #1 topic where freshers lose technical interviews. It is deceptively easy to claim and genuinely hard to demonstrate. The winning formula: every answer needs (1) a one-line definition, (2) a real-world analogy, (3) a short code snippet, and (4) a practical reason it exists.

🔷 4 Pillars🔒 Encapsulation🧬 Inheritance🔀 Polymorphism🎭 Abstraction🏗 Classes🔑 Keywords📐 SOLID🧩 Patterns⚡ Advanced
Overview

🔷 Why OOPs Questions Are Fresher Killers

⚡ The Real Problem

Almost every fresher can define the 4 pillars. Almost none can demonstrate them in code. The winning move: every answer needs (1) one-line definition, (2) real-world analogy, (3) short code snippet, (4) practical reason it exists. Without all four, the answer sounds memorized, not understood.

"Object-oriented programming forces you to think about the world as interacting, encapsulated entities with clear responsibilities. That mental model is the real skill — not the syntax."
— Edsger W. Dijkstra · Turing Award Winner · Pioneer of structured programming
The 4-Part Formula for Every OOPs Answer
➊ DEFINE IT — one crisp sentence, no jargon ➋ ANALOGY — real-world parallel (ATM, car, animal hierarchy) ➌ CODE IT — 8–15 lines that actually demonstrate the concept ➍ WHY IT EXISTS — what problem it solves, what breaks without it
Reference

🔷 The 4 Pillars — Quick Reference

E

Encapsulation

Bundle data + methods. Hide internals via access modifiers. Expose only what is needed.

I

Inheritance

Child acquires properties of parent. Code reuse and IS-A relationships.

P

Polymorphism

Same interface, different behavior. One method name, many implementations.

A

Abstraction

Hide complexity. Expose only essential features. Users interact with interface, not internals.

PillarReal-World AnalogyAchieved ViaWithout It
EncapsulationCar engine — you use accelerator, not fuel injectorprivate fields + public getters/settersAny code can corrupt any object's state
InheritanceDog IS-A Animal — gets eat()/sleep(), adds bark()extends keywordCopy-paste code everywhere
Polymorphismdraw() on circle vs square — same call, different resultMethod overriding + interfacesGiant if-else chains for every object type
AbstractionATM — press "Withdraw", don't see the DB callsabstract class, interfaceComplexity leaks everywhere
Questions 1–2

🔒 Encapsulation

⚡ Why Encapsulation Is the Foundation of Safe Code

Without encapsulation, any code can directly modify any object's internal state. A bank account where anyone writes directly to the balance field vs one where all changes go through validated deposit()/withdraw() methods — that difference is encapsulation in action.

Definition: Bundling data (fields) and the methods that operate on them into a single unit (class), restricting direct external access using access modifiers.

Analogy: A bank account — you cannot write directly to the balance field. All changes go through deposit() or withdraw() with validation.

public class BankAccount {
    private double balance;        // PRIVATE - no direct access
    private String owner;

    public BankAccount(String owner, double initial) {
        this.owner = owner;  this.balance = initial;
    }

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Invalid");
        balance += amount;    // validated before state change
    }

    public boolean withdraw(double amount) {
        if (amount > balance) return false;
        balance -= amount;  return true;
    }

    public double getBalance() { return balance; }  // read-only, no setter
}

account.balance = -99999;   // Without encapsulation: state corrupted
account.withdraw(99999);    // With encapsulation: fails gracefully
  • Data protection: invalid states prevented at the source
  • Maintainability: internal changes do not break external code
  • Controlled access: read-only, write-only, or computed fields
💡 Encapsulation enables the Open/Closed Principle — change storage from double to BigDecimal? External code never knows. The public API stays identical.
ModifierSame ClassSame PackageSubclass (diff pkg)Everywhere
private
default
protected
public
public class Employee {
    private   String ssn;      // most restrictive - this class only
              int    id;       // default - this package only
    protected String name;     // package + subclasses
    public    String dept;     // everywhere
}
📌 Design rule: start with private for all fields. Expose only what external code truly needs. Use protected when subclasses specifically need access.
Questions 3–5

🧬 Inheritance

⚡ Why Inheritance Is Both Powerful and Dangerous

Only use inheritance for genuine IS-A relationships. Java's own Stack extends Vector — exposing 43 extra methods that break the Stack contract. This mistake is cited in every OOP textbook as a cautionary tale.

Definition: A mechanism where a child (subclass) acquires the properties and behaviors of a parent (superclass), enabling code reuse and IS-A relationships.

class Animal {
    String name;
    void eat()  { System.out.println(name + " is eating"); }
    void sleep(){ System.out.println(name + " is sleeping"); }
}

class Dog extends Animal {    // Dog IS-A Animal
    void bark(){ System.out.println(name + " says Woof!"); }
}

Dog d = new Dog();  d.name = "Rex";
d.eat();   // inherited from Animal
d.bark();  // Dog's own method

Types: Single (A→B), Multilevel (A→B→C), Hierarchical (A→B and A→C) — all in Java. Multiple class inheritance: NOT supported (Diamond Problem). Multiple interface: YES.

IS-A vs HAS-A: Dog IS-A Animal → extends. Car HAS-A Engine → use a field. Prefer composition when no genuine IS-A relationship exists.

⚠ Only use inheritance for genuine IS-A. If inheriting purely for code reuse, use composition.

When class D inherits from B and C, both of which override the same method from A — the compiler cannot resolve which version D should use.

// Hypothetical - NOT valid Java:
class A           { void greet() { print("A"); } }
class B extends A { void greet() { print("B"); } }
class C extends A { void greet() { print("C"); } }
class D extends B, C { }  // Which greet() does D use? Ambiguous!

// Java solution: disallow multiple class inheritance.
// Allow multiple INTERFACE implementation with explicit resolution:
interface B { default void greet() { print("B"); } }
interface C { default void greet() { print("C"); } }
class D implements B, C {
    public void greet() { B.super.greet(); }  // must explicitly choose
}
💡 Interfaces have no instance state, so multiple implementation causes no ambiguity about data — only about default methods, which must be explicitly resolved.
class Animal {
    String name;  int age;
    Animal(String name, int age){ this.name=name; this.age=age; }
    void describe(){ System.out.println("Animal: " + name); }
}

class Dog extends Animal {
    String breed;

    // USE 1: super() calls parent constructor - MUST be first line
    Dog(String name, int age, String breed){
        super(name, age);    // initialises Animal fields
        this.breed = breed;
    }

    // USE 2: super.method() calls parent's overridden method
    void describe(){
        super.describe();          // "Animal: Rex"
        System.out.println("Breed: " + breed);
    }

    // USE 3: super.field accesses parent field when shadowed
    String getParentName(){ return super.name; }
}
🧠 If you do not call super() explicitly, Java inserts super() (no-arg) automatically as the first statement. If the parent has no no-arg constructor, you MUST explicitly call super(args).
Questions 6–8

🔀 Polymorphism

⚡ Why Polymorphism Is the Most Powerful OOP Concept

Without polymorphism, you need a giant if-else every time you process different object types. With it, code written against an interface works for every type that implements it — including types that did not exist when you wrote the code. This is the foundation of every plugin system and framework hook in existence.

Compile-time (Method Overloading): Same method name, different parameter signatures in the same class. Resolved at compile time — static binding.

class Calculator {
    int    add(int a, int b)         { return a+b; }
    double add(double a, double b)   { return a+b; }
    int    add(int a, int b, int c)  { return a+b+c; }
}
calc.add(1,2);      // compile time: add(int,int)
calc.add(1.5,2.5);  // compile time: add(double,double)

Runtime (Method Overriding): Child redefines parent's method. Resolved at runtime based on actual object type — dynamic dispatch.

class Shape     { void draw(){ System.out.println("Shape");  } }
class Circle    extends Shape { void draw(){ System.out.println("Drawing a circle"); } }
class Rectangle extends Shape { void draw(){ System.out.println("Drawing a rectangle"); } }
class Triangle  extends Shape { void draw(){ System.out.println("Drawing a triangle"); } }

Shape[] shapes = { new Circle(), new Rectangle(), new Triangle() };
for(Shape s : shapes) s.draw();
// One loop handles ALL shape types, including future ones.
// Add Pentagon? Just extend Shape. ZERO changes to this loop.
💡 The real power: this loop was written once and automatically handles every future Shape subtype. That is how frameworks call your custom code without knowing it exists.
PropertyOverloadingOverriding
DefinitionSame name, different params — SAME classChild redefines parent method — SAME signature
BindingCompile-time (static)Runtime (dynamic)
SignatureMUST differMUST be identical
Return typeCan differSame or covariant
Access modifierAnythingCannot reduce visibility
Static methods?✓ Can overload✗ Static methods are HIDDEN, not overridden
class Parent {
    public void   show()            { System.out.println("Parent show"); }
    public void   show(String msg)  { System.out.println("Parent: " + msg); }
}
class Child extends Parent {
    @Override
    public void show()              { System.out.println("Child show"); }  // overriding
    public void show(int n)         { System.out.println("Child: " + n); } // overloading
}

Parent p = new Child();
p.show();        // "Child show"  - runtime dispatch (overriding)
p.show("hi");    // "Parent: hi"  - compile-time (overloading)
⚠ Always use @Override when intending to override. If you accidentally change the signature, it becomes an overload. @Override catches this at compile time.

No. Static methods belong to the class, not instances. They are resolved at compile time by reference type. When a child defines a static method with the same signature, it is called method hiding.

class Parent {
    static void  staticMethod()  { System.out.println("Parent static"); }
    void         instanceMethod(){ System.out.println("Parent instance"); }
}
class Child extends Parent {
    static void  staticMethod()  { System.out.println("Child static"); }   // HIDING
    void         instanceMethod(){ System.out.println("Child instance"); } // OVERRIDING
}

Parent ref = new Child();
ref.staticMethod();    // "Parent static"  - REFERENCE TYPE decides (compile time)
ref.instanceMethod();  // "Child instance" - ACTUAL OBJECT decides (runtime)
🧠 Instance methods use dynamic dispatch (runtime polymorphism). Static methods use static dispatch (compile-time). There is no vtable lookup for static methods.
Questions 9–10

🎭 Abstraction

⚡ Why Abstraction Manages Complexity

Abstraction is why you can use HashMap without knowing its hash function, or use JDBC without knowing your database protocol. It allows systems to grow — no single engineer needs to understand the entire stack to contribute.

PropertyAbstract ClassInterface
MethodsAbstract AND concreteAbstract by default; default/static (Java 8+)
FieldsAny visibility, instance variablespublic static final constants only
Constructor✓ Yes✗ No
Multiple inheritance✗ Extend only ONE✓ Implement MANY
Use whenPartial impl + shared state + IS-AContract/capability + multiple inheritance of type
// Abstract class: partial implementation + shared state
abstract class Vehicle {
    private String color;
    public Vehicle(String color){ this.color = color; }
    abstract void refuel();      // subclasses MUST implement
    public void start(){ System.out.println(color + " engine started"); }
}
class Car extends Vehicle {
    public Car(String c){ super(c); }
    public void refuel(){ System.out.println("Filling petrol"); }
}

// Interface: capability contract
interface Flyable   { void fly(); }
interface Swimmable { void swim(); }
class Duck extends Animal implements Flyable, Swimmable {
    public void fly() { System.out.println("Duck flies"); }
    public void swim(){ System.out.println("Duck swims"); }
}
// Duck IS-A Animal, and is BOTH Flyable and Swimmable
📌 Decision rule: need shared state (instance vars) or partial impl? → abstract class. Defining a capability contract for many unrelated classes? → interface. When in doubt: use an interface (Java 8+ default methods make them nearly as powerful).
AspectAbstractionEncapsulation
HidesComplexity — the HOWData — internal WHAT
Question"What does this DO?""How does it PROTECT its data?"
LevelDesign/architectureImplementation/coding
Achieved viaInterfaces, abstract classesprivate fields + public methods

Analogy: ATM machine — Abstraction: you see "Withdraw" button, not the DB calls (complexity hidden). Encapsulation: the cash tray — you cannot reach in directly; all interactions go through the process (data protected).

🧠 Abstraction defines WHAT this account supports. Encapsulation enforces HOW you access it. Abstraction = architect's concern; Encapsulation = builder's concern.
Questions 11–15

🏗 Classes, Objects & Constructors

⚡ Why Constructor Questions Trip Up Freshers

Constructor chaining, this() vs super() ordering rules, private constructors for Singleton, and copy constructors appear regularly in Infosys technical rounds. These are daily Java mechanics every developer uses.

Class: Blueprint defining attributes and behaviors. No memory allocated for instance data at definition time. Object: A specific instance. Memory allocated on the heap when new is called.

class Car { String brand; int year; void start(){ System.out.println(brand + " started"); } }

Car car1 = new Car();  car1.brand = "Toyota";  // heap memory for car1
Car car2 = new Car();  car2.brand = "Honda";   // separate heap memory
car1.start();  // "Toyota started" - same method, different data
car2.start();  // "Honda started"

Memory layout: Stack → reference variable. Heap → actual object data (GC-managed). Method Area → class metadata and bytecode shared across all instances.

class Student {
    String name;  int age;  String major;

    // 1. DEFAULT: chains to parameterized via this()
    Student(){ this("Unknown", 18, "Undecided"); }

    // 2. PARAMETERIZED: initialises all fields
    Student(String name, int age, String major){
        this.name = name;  this.age = age;  this.major = major;
    }

    // 3. COPY: creates new object from existing one
    Student(Student other){ this(other.name, other.age, other.major); }
}

// Rules:
// this(...) - calls another constructor in SAME class, must be FIRST line
// super(...) - calls parent constructor, must be FIRST line
// Cannot call BOTH this() AND super() in the same constructor
💡 If you define ANY constructor, Java stops auto-generating the no-arg default. Frameworks (Spring, Hibernate) often require a no-arg constructor — write it explicitly when needed.

Yes. A private constructor prevents external code from calling new ClassName() directly.

// Singleton - double-checked locking
public class AppConfig {
    private static volatile AppConfig instance;
    private AppConfig(){ }     // private - no external instantiation

    public static AppConfig getInstance(){
        if(instance == null){
            synchronized(AppConfig.class){
                if(instance == null) instance = new AppConfig();
            }
        }
        return instance;
    }
}

// Enum Singleton (BEST - JVM-guaranteed thread-safe + serialization-safe)
public enum Config { INSTANCE;  public String getDb(){ return "jdbc:..."; } }
Config.INSTANCE.getDb();  // guaranteed single instance

// Static factory methods - named, expressive constructors
public class Color {
    private int r, g, b;
    private Color(int r, int g, int b){ this.r=r; this.g=g; this.b=b; }
    public static Color red()           { return new Color(255, 0, 0); }
    public static Color fromHex(String h){ /* parse */ return new Color(r, g, b); }
}
public class Person {
    private String name;  private int age;

    // USE 1: disambiguate instance variable from parameter
    public Person(String name, int age){ this.name = name;  this.age = age; }

    // USE 2: call another constructor in same class (FIRST line)
    public Person(String name){ this(name, 0); }

    // USE 3: pass current object as argument
    public void register(Registry r){ r.add(this); }

    // USE 4: return current object - enables fluent builder chaining
    public Person setName(String n){ this.name = n;  return this; }
    public Person setAge(int a)    { this.age  = a;  return this; }
}

// Builder chaining:
Person p = new Person("Alice").setName("Alice Chen").setAge(25);
RelationshipOwnershipLifecycleExample
AssociationNoneIndependentStudent has a Teacher
AggregationWeak — parts independentParts survive the wholeLibrary HAS Books (books survive if library closes)
CompositionStrong — owns exclusivelyParts destroyed with wholeHuman HAS Heart (heart cannot exist without Human)
// AGGREGATION: Engine passed in from outside
class Car { private Engine engine; Car(Engine e){ this.engine = e; } }

// COMPOSITION: Rooms created inside, destroyed with House
class House {
    private final Room[] rooms;
    House(int n){ rooms = new Room[n];  for(int i=0;i<n;i++) rooms[i] = new Room(); }
}
📌 Prefer Composition over Inheritance (GoF principle): composition is more flexible — you can swap the contained object at runtime. Inheritance is static. Use composition for HAS-A; use inheritance only for genuine IS-A.
Questions 16–18

🔑 Keywords & Modifiers

⚡ Why static, final, and Exceptions Are Always Tested

These appear in every Infosys question set as "What happens when..." scenarios. Understanding what each prevents and the edge cases separates solid Java knowledge from surface-level familiarity.

class Counter {
    private static int count = 0;  // STATIC FIELD: shared by ALL instances
    private int id;                // instance field: each object has its own

    static {                       // STATIC BLOCK: runs ONCE when class loads
        count = 100;
        System.out.println("Counter class loaded");
    }

    public Counter(){ this.id = ++count; }

    // STATIC METHOD: no 'this', cannot access instance fields
    public static int getCount(){ return count; }
    // public static int getId(){ return this.id; }  // compile error - no 'this'
}

// STATIC NESTED CLASS: no outer instance needed
class Outer {
    private int x = 10;
    static class Nested { void show(){ System.out.println("nested"); } }
    class Inner         { void show(){ System.out.println(x); } }  // CAN access x
}
⚠ A static method cannot access non-static fields — there is no implicit this. This is why main() must be static: it runs before any object is created.
// 1. FINAL VARIABLE: cannot be reassigned after initialization
final int MAX = 100;
MAX = 200;                    // compile error

final StringBuilder sb = new StringBuilder("hello");
sb.append(" world");          // OK - mutating the OBJECT is fine
sb = new StringBuilder();     // compile error - reassigning the REFERENCE is not
// CRUCIAL: final prevents reference reassignment, NOT object mutation!

// 2. FINAL METHOD: cannot be overridden
class Base  { final void critical(){ } }
class Child extends Base { void critical(){ } }  // compile error

// 3. FINAL CLASS: cannot be subclassed
// Java's String is final:
// 1. Security: cannot override hashCode/equals used as HashMap keys
// 2. Thread safety: immutable = no synchronization needed
// 3. String pool: JVM can safely reuse interned literals
💡 static final = compile-time constant. private final = instance constant set in constructor. final parameter = cannot be reassigned inside method body (needed for lambda variable capture).
PropertyCheckedUnchecked
Detected atCompile time — enforcedRuntime — optional to handle
Must handle?✓ Yes — try-catch or throws✗ No
IndicatesRecoverable external conditionsProgramming errors (bugs)
ExamplesIOException, SQLException, ClassNotFoundExceptionNullPointerException, IllegalArgumentException, ArrayIndexOutOfBoundsException
// CHECKED: predictable external failure - caller MUST handle
void readFile(String path) throws IOException {
    new BufferedReader(new FileReader(path));
}

// UNCHECKED: programmer error - fix it, do not catch it
void setAmount(double amt){
    if(amt < 0) throw new IllegalArgumentException("Amount negative: " + amt);
}

// Exception hierarchy:
//              Throwable
//             /         //       Exception       Error         (Error: JVM issues, do not catch)
//      /          // Checked       RuntimeException      (Unchecked)
📌 Rule: external conditions the caller can recover from → checked. Programming mistakes that should be fixed in code → unchecked (IllegalArgumentException, IllegalStateException).
Questions 19–20

☕ Java-Specific Internals

⚡ Why Java Questions Appear in Every Infosys Round

Infosys primarily uses Java. String immutability, == vs .equals(), StringBuilder vs StringBuffer, and garbage collection are the most common Java-specific OOP questions. These reveal whether you actually write Java or just know its syntax.

PropertyStringStringBufferStringBuilder
Mutable?✗ Immutable✓ Mutable✓ Mutable
Thread-safe?✓ Yes✓ Yes (synchronized)✗ No
Use whenSimple strings, HashMap keysMulti-threaded string buildingSingle-threaded string building (99% of cases)
// BAD: creates 10,000 String objects in heap - O(n squared)
String result = "";
for(int i = 0; i < 10000; i++) result += "item" + i;  // each += creates new object

// GOOD: one mutable object, amortized O(1) per append
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 10000; i++) sb.append("item").append(i);
String result = sb.toString();

// Why String is immutable:
// 1. String Pool: JVM reuses identical literals safely
// 2. Thread safety: immutable = no synchronization needed
// 3. HashMap key safety: hashCode() is stable after insertion
// 4. Security: prevents class-loading attacks
🧠 == compares references (same heap object?). .equals() compares content. "hello" == "hello" may be true (String pool). new String("hello") == new String("hello") is always false. ALWAYS use .equals() to compare String content.

Java's GC automatically reclaims heap memory occupied by objects no longer reachable from any live root reference.

Generational GC: Young Generation (Eden + Survivor) → Minor GC (frequent, cheap, most objects die here). Old Generation → Full GC (infrequent, expensive). Metaspace (Java 8+) → class metadata, not heap.

Object obj = new Object();
obj = null;     // unreachable - eligible for GC
System.gc();    // hint only - JVM decides WHEN, NOT guaranteed

// finalize(): deprecated Java 9+, removed Java 18.
// Problems: no timing guarantee, silent exception swallowing, can resurrect objects.

// CORRECT approach: AutoCloseable + try-with-resources (deterministic cleanup)
class DBConn implements AutoCloseable {
    public void close(){ System.out.println("Connection closed"); }
}
try(DBConn c = new DBConn()){ /* use c */ }  // close() called automatically
💡 Modern JVMs use G1GC by default (Java 9+) — divides heap into equal regions, prioritizes most garbage-dense regions, predictably meets pause time targets. For ultra-low-latency: ZGC or Shenandoah GC.
Questions 21–26

📐 SOLID Principles

⚡ Why SOLID Is Now a Standard Fresher Question

SOLID reveals whether you think about code quality and maintainability, not just functionality. One sentence per principle plus a concrete before/after example is what impresses Infosys interviewers.

S

Single Responsibility

A class should have only ONE reason to change — one job, done well.

O

Open / Closed

Open for extension (new behavior via new classes). Closed for modification (do not change working code).

L

Liskov Substitution

Subtype objects must be substitutable for supertype objects without breaking the program.

I

Interface Segregation

Many small focused interfaces beat one large bloated one. Clients should not depend on methods they do not use.

D

Dependency Inversion

Depend on abstractions (interfaces), not concrete implementations.

// VIOLATES SRP: UserManager has 3 reasons to change
class UserManager {
    void createUser(String name) { /* DB insert */ }
    void sendEmail(String email)  { /* SMTP logic */ }
    void genReport(String id)     { /* PDF gen */ }
}
// Change email provider  -> modify UserManager
// Change PDF library     -> modify UserManager
// Change DB schema       -> modify UserManager

// FOLLOWS SRP: each class has ONE responsibility
class UserRepository  { void save(User u){ } }
class EmailService    { void send(User u){ } }
class ReportGenerator { void gen(User u) { } }
// Change email provider -> only EmailService changes. Others untouched.
// VIOLATES OCP: every new discount type requires modifying this method
double discount(String type, double price){
    if(type.equals("Regular")) return price * 0.05;
    if(type.equals("Premium")) return price * 0.10;
    // Adding "Corporate"? Must MODIFY this method - risky!
    return 0;
}

// FOLLOWS OCP: new types extend the interface - zero existing code changed
interface DiscountStrategy  { double apply(double p); }
class RegularDiscount  implements DiscountStrategy { public double apply(double p){ return p*0.05; } }
class PremiumDiscount  implements DiscountStrategy { public double apply(double p){ return p*0.10; } }
class CorporateDiscount implements DiscountStrategy { public double apply(double p){ return p*0.25; } }
// Add "Enterprise"? Just add a new class. Zero changes to existing code.
💡 OCP is why well-designed frameworks are extensible — you write your plugins, the framework calls them without you modifying the framework source. Spring beans, Java's Comparator, JDBC drivers — all OCP in action.
// VIOLATES LSP: Square is not a proper behavioral subtype of Rectangle
class Rectangle {
    protected int width, height;
    void setWidth(int w)  { this.width = w; }
    void setHeight(int h) { this.height = h; }
    int  area()           { return width * height; }
}
class Square extends Rectangle {
    void setWidth(int w)  { this.width = w;  this.height = w; } // side effect!
    void setHeight(int h) { this.width = h;  this.height = h; }
}

void test(Rectangle r){
    r.setWidth(5);  r.setHeight(10);
    assert r.area() == 50;  // PASSES for Rectangle, FAILS for Square (area=100)
}

// Fix: do not inherit just because of geometric "is-a".
// Mathematical is-a does not equal behavioral is-a in OOP.
// Both should independently implement a Shape interface.
// VIOLATES ISP: fat interface forces animals to implement irrelevant methods
interface Animal {
    void eat();   void sleep();
    void fly();   // Penguins cannot fly - forced to throw UnsupportedOperationException
    void swim();  // Eagles do not swim - forced empty implementation
}

// FOLLOWS ISP: small focused interfaces
interface Eatable  { void eat(); }
interface Sleepable{ void sleep(); }
interface Flyable  { void fly(); }
interface Swimmable{ void swim(); }

class Eagle   implements Eatable, Sleepable, Flyable             { }
class Penguin implements Eatable, Sleepable, Swimmable           { }
class Duck    implements Eatable, Sleepable, Flyable, Swimmable  { }
// VIOLATES DIP: OrderService directly coupled to MySQLDatabase
class OrderService {
    private MySQLDatabase db = new MySQLDatabase();  // tightly coupled!
    void placeOrder(String o){ db.save(o); }
}
// Switching to MongoDB requires changing OrderService - violation!

// FOLLOWS DIP: depends on ABSTRACTION
interface Database { void save(String data); }
class MySQLDatabase   implements Database { public void save(String d){ System.out.println("MySQL: " + d); } }
class MongoDatabase   implements Database { public void save(String d){ System.out.println("Mongo: " + d); } }

class OrderService {
    private final Database db;
    OrderService(Database db){ this.db = db; }  // INJECTED - loose coupling
    void placeOrder(String o){ db.save(o); }
}

// Switch to MongoDB? Just inject MongoDatabase. OrderService unchanged.
OrderService svc = new OrderService(new MongoDatabase());
💡 DIP is the foundation of Spring Framework's IoC container. You program against interfaces; Spring decides which concrete implementation to wire in based on configuration.

Cohesion: How strongly related are the responsibilities within a single class? High cohesion = does one thing well. Low cohesion = does many unrelated things.

Coupling: How dependent are modules on each other? Low coupling = change independently. High coupling = changing one forces changes in others.

CombinationOutcome
High cohesion + Low coupling✓ Ideal — maintainable, testable, extensible
Low cohesion + High coupling✗ Worst — spaghetti code
// LOW COHESION: God class doing unrelated things
class GodClass { void parseJSON(){} void sendEmail(){} void generatePDF(){} }

// HIGH COHESION: each class focused on one responsibility
class JsonParser    { Object parse(String json){ return null; } }
class EmailSender   { void   send(String to, String body){ } }
class PdfGenerator  { byte[] generate(Object data){ return null; } }
🧠 The SOLID principles are essentially rules for achieving high cohesion and low coupling. Apply them consistently and your code will be naturally more maintainable, testable, and extensible.
Questions 27–30

🧩 Design Patterns

⚡ Why Design Patterns Are Now Fresher Interview Territory

Singleton, Factory, Observer, and Strategy are standard fresher questions at Infosys for DSE and SP roles. You need to explain the pattern, write its code, and explain the real problem it solves. Definitions alone will not pass.

Problem it solves: Ensure only one instance of a class exists throughout the application (logger, config, DB connection pool).

// VERSION 1: Not thread-safe (avoid)
class Logger {
    private static Logger instance;
    private Logger(){ }
    public static Logger getInstance(){
        if(instance == null) instance = new Logger();  // race condition!
        return instance;
    }
}

// VERSION 2: Double-checked locking (thread-safe + efficient)
class Logger {
    private static volatile Logger instance;  // volatile prevents reordering
    private Logger(){ }
    public static Logger getInstance(){
        if(instance == null){
            synchronized(Logger.class){
                if(instance == null) instance = new Logger();
            }
        }
        return instance;
    }
}

// VERSION 3: Enum Singleton (BEST - JVM-guaranteed, serialization-safe)
public enum AppConfig {
    INSTANCE;
    public String getDbUrl(){ return "jdbc:mysql://..."; }
}
AppConfig.INSTANCE.getDbUrl();  // guaranteed single instance
⚠ Singletons make unit testing hard — they carry state between tests. Consider Spring's DI container to manage the singleton lifecycle, which maintains single-instance behavior while keeping the class testable with mocks.

Problem it solves: The exact type of object to create is determined at runtime. Using new ConcreteClass() directly couples code to specific implementations.

interface Document    { void open();  void save(); }
class PdfDocument  implements Document { public void open(){ System.out.println("PDF open"); }  public void save(){ } }
class WordDocument implements Document { public void open(){ System.out.println("Word open"); } public void save(){ } }

// Factory: one centralized place for all creation logic
class DocumentFactory {
    public static Document create(String type){
        return switch(type.toLowerCase()){
            case "pdf"  -> new PdfDocument();
            case "word" -> new WordDocument();
            default     -> throw new IllegalArgumentException("Unknown: " + type);
        };
    }
}

// Client does not know WHICH concrete class it gets
Document doc = DocumentFactory.create("pdf");
doc.open();  // works for any Document type
// Add Excel support? New class + one case in factory. Client code unchanged.

Problem it solves: One object (publisher) needs to notify many observers when its state changes — without knowing who those observers are.

interface StockObserver { void update(String stock, double price); }

class StockMarket {                          // Publisher
    private List<StockObserver> list = new ArrayList<>();
    public void subscribe(StockObserver o)   { list.add(o); }
    public void unsubscribe(StockObserver o) { list.remove(o); }
    public void setPrice(String s, double p) {
        list.forEach(o -> o.update(s, p));  // notify ALL observers
    }
}

class EmailAlert implements StockObserver { public void update(String s,double p){ System.out.println("Email: " + s + "=" + p); } }
class MobilePush implements StockObserver { public void update(String s,double p){ System.out.println("Push: "  + s + "=" + p); } }
class TradingBot  implements StockObserver { public void update(String s,double p){ System.out.println("Bot trading: " + s); } }

StockMarket mkt = new StockMarket();
mkt.subscribe(new EmailAlert()); mkt.subscribe(new MobilePush()); mkt.subscribe(new TradingBot());
mkt.setPrice("INFY", 1750.50);  // all 3 notified automatically
💡 Observer is everywhere in real code: Java's ActionListener, React state management, Spring ApplicationEvents, JavaScript's addEventListener — all Observer pattern implementations.

Problem it solves: Multiple algorithms for the same task — switch between them at runtime without if-else chains.

interface SortStrategy { void sort(int[] arr); }
class BubbleSort implements SortStrategy { public void sort(int[] a){ System.out.println("Bubble"); } }
class MergeSort  implements SortStrategy { public void sort(int[] a){ System.out.println("Merge");  } }
class QuickSort  implements SortStrategy { public void sort(int[] a){ System.out.println("Quick");  } }

class Sorter {
    private SortStrategy strategy;
    Sorter(SortStrategy s){ this.strategy = s; }
    void setStrategy(SortStrategy s){ this.strategy = s; }
    void sort(int[] arr){ strategy.sort(arr); }  // delegates to current strategy
}

Sorter sorter = new Sorter(new QuickSort());
sorter.sort(data);                      // uses QuickSort
sorter.setStrategy(new MergeSort());    // swap at runtime
sorter.sort(data);                      // now uses MergeSort
// Java's Comparator IS a strategy: Collections.sort(list, comparator)
Questions 31–32

⚡ Advanced OOP Concepts

Declare variables, parameters, and return types using interfaces (or abstract types), not concrete classes. Let the concrete implementation be determined at runtime or by injection.

// Tightly coupled to ArrayList
ArrayList<String> list = new ArrayList<>();
// Switching to LinkedList requires finding and changing every usage.

// Loosely coupled - programmed to interface
List<String> list = new ArrayList<>();    // declare as List (interface)
// Switch to LinkedList? ONE change, nothing else breaks:
// List<String> list = new LinkedList<>();

// Powerful real-world example: payment gateway abstraction
interface PaymentProcessor { boolean process(double amount); }

class PaymentService {
    private final PaymentProcessor processor;   // depends on INTERFACE
    PaymentService(PaymentProcessor p){ this.processor = p; }
    boolean pay(double amt){ return processor.process(amt); }
}

PaymentService live = new PaymentService(new RazorpayProcessor());
PaymentService test = new PaymentService(new MockPaymentProcessor()); // for unit tests
🧠 This principle directly enables DIP (SOLID), testability (inject mocks in tests), and the entire ecosystem of framework design — Spring's ApplicationContext, JDBC drivers, SLF4J logging facade. It is not just a preference; it is the foundation of enterprise architecture.
// == compares REFERENCES (same object in heap?)
// .equals() compares CONTENT (same logical value?)

String s1 = "hello";              // from String pool
String s2 = "hello";              // same pool object
String s3 = new String("hello");  // new heap object, NOT from pool

System.out.println(s1 == s2);        // TRUE  - same pool object
System.out.println(s1 == s3);        // FALSE - s3 is a different heap object
System.out.println(s1.equals(s3));   // TRUE  - same content

// Integer cache trap (values -128 to 127):
Integer a = 127;  Integer b = 127;  System.out.println(a == b);  // TRUE (cached)
Integer c = 128;  Integer d = 128;  System.out.println(c == d);  // FALSE (not cached)
System.out.println(c.equals(d));                                   // TRUE

The String Pool: Java maintains a pool of String literals in JVM non-heap memory. When you write "hello" as a literal, the JVM checks if that value already exists. If yes, the existing reference is reused (memory saving). new String("hello") always creates a new heap object, bypassing the pool.

// Explicitly intern a String back into the pool:
String s4 = new String("hello").intern();  // now references the pool object
System.out.println(s1 == s4);              // TRUE
⚠ ALWAYS use .equals() to compare String values — never ==. String pool behavior depends on JVM, compile-time constant folding, and runtime conditions. Code relying on == for Strings is a latent bug.
· · ·
Summary

🗀 OOPs Quick-Review Cheatsheet

"Programs must be written for people to read, and only incidentally for machines to execute. OOP, done right, achieves exactly that."
— Harold Abelson · MIT · Co-author of "Structure and Interpretation of Computer Programs"
12 OOPs Rules to Know Cold Before Any Interview
1. 4 pillars: Encapsulation (private+getters) · Inheritance (extends) · Polymorphism (override) · Abstraction (interface/abstract) 2. Overloading = same class, different params, compile-time · Overriding = subclass, same sig, runtime dispatch 3. Abstract class: partial impl + constructor + instance vars · Interface: pure contract + multiple inheritance 4. Static methods are HIDDEN not overridden — resolved by reference type at compile time 5. final variable: cannot reassign reference · final method: cannot override · final class: cannot extend 6. super() must be FIRST statement in child constructor. Cannot call both this() AND super(). 7. Composition over Inheritance for HAS-A (more flexible, less coupled, easier to test) 8. SOLID: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion 9. Singleton: private constructor + volatile static + double-checked locking (or use enum) 10. Factory: centralized creation · Observer: one-to-many notification · Strategy: swappable algorithms 11. == compares references · .equals() compares content · ALWAYS use .equals() for String comparison 12. Checked exceptions: must handle (external failures) · Unchecked: optional (programmer errors)

Design Patterns Quick Reference

PatternCategoryProblem SolvedKey Structure
SingletonCreationalExactly one global instancePrivate constructor + volatile static + getInstance()
FactoryCreationalObject type decided at runtimeInterface + concrete classes + static factory method
BuilderCreationalComplex object with many optional paramsFluent setters returning this + build()
ObserverBehavioralOne-to-many state change notificationSubject + Observer interface + subscribe/notify list
StrategyBehavioralSwappable algorithms at runtimeStrategy interface + concrete strategies + context
DecoratorStructuralAdd behavior without subclassingComponent interface + concrete + wrapper
AdapterStructuralIncompatible interfaces need to work togetherTarget interface + Adaptee + Adapter wrapping Adaptee

Up Next: Phase 4 — DBMS

32 DBMS questions — beyond SQL into how databases work internally: transactions, B-tree indexing, deadlocks, MVCC, connection pools, and modern database architecture.

Phase 4: DBMS →