Method overriding

Every payment has a fee, but a UPI payment's fee is zero and a card's is 1.8%. Instead of a long if-else on the payment type, each class can supply its own version of fee(). That's method overriding. You'll learn the exact rules javac enforces, how @Override catches silent bugs, why static methods are hidden rather than overridden, how the JVM picks the version at run time, and then practise with 10 problems.

Module 7 · OOP Pillars⏱ 24 minIntermediate10 practice problems
1 The story

The if-else that kept growing

Riya's fest ticketing app charges a convenience fee. Her fee() method has if (type.equals("CARD")), else if (type.equals("UPI")), else if ("WALLET"). When the team adds net banking, three different files need a new else if, and one is missed: net banking users pay the card fee.

double fee(String type) { if (type.equals("CARD")) ... else if (type.equals("UPI")) ... else if (type.equals("WALLET")) // NETBANKING forgotten! } Payment fee() → 2% CardUpiNetBanking fee() → 1.8%fee() → 0fee() → Rs 10 a new method = one new class, nothing else changes myphatshala.in
Riya

Sir, every new payment type means hunting for every if-else in the code.

Arjun Sir

Let each payment class answer for itself. Payment has a default fee(), and UpiPayment writes its own fee() with the same name and parameters. That's overriding.

Riya

And how does Java know which fee() to run?

Arjun Sir

At run time it looks at the actual object. A UPI object runs the UPI version, even through a Payment variable. Net banking becomes one new class, and no old code changes.

2 Definition

What is method overriding?

Formal definition (write this in exams)

Method overriding occurs when a subclass declares an instance method with the same name and the same parameter list (the same signature) as an accessible instance method of its superclass, with a compatible return type. The subclass's version replaces the inherited one for objects of the subclass, and the JVM chooses which version to execute at run time based on the actual class of the object (dynamic method dispatch). Static, private and final methods cannot be overridden.

In simple words

The parent says "every payment has a fee". Each child says "here's how my fee is calculated". Same question, different answer depending on who you ask.

Words you'll meet in this lesson

SignatureMethod name + parameter types, in order. The return type is not part of it.
@OverrideAn annotation asking javac to confirm the method really overrides something.
Covariant returnThe override may return a subclass of the parent's return type.
Method hidingA static method with the same signature in a child: chosen by the variable's type, not overridden.
Dynamic dispatchChoosing the method version at run time from the object's class.
OverloadingSame name, different parameters; chosen at compile time (Lesson 5.4).

The overriding rules

SignatureExactly the same name and parameter types. Different parameters = overloading, not overriding.
Return typeSame, or a subtype (covariant) for objects. For primitives it must be identical.
AccessSame or wider: protected → public is fine; public → package-private is an error ("weaker access privileges").
Checked exceptionsThe override may throw fewer or narrower checked exceptions, never new or broader ones. Unchecked ones are free.
Can't overridefinal (error), static (hidden instead), private (not inherited; a same-named method is just a new method)
Which version runsDecided at run time by the object's class, not the variable's type

Exam tip: for access, remember the order private < default < protected < public. An overriding method can move right (wider) but never left. And constructors are never overridden, because they aren't inherited.

3 Types and ways to use it

Three ways to override

Replace

The child's version does something entirely different: UPI's fee is just 0.

new logic

Extend

Call super.m() and add to it: a manager's salary = employee's + allowance.

super.m() + more

Narrow the return

Parent returns Shape, child returns Circle: callers of the child get the exact type.

covariant return

The rules, compiled

class Document {
    protected String name = "doc";

    protected String title() { return "Document"; }    // will be widened

    Document copy() { return new Document(); }         // will be narrowed

    void print() throws Exception { System.out.println("printing " + name); }
}

class Invoice extends Document {
    @Override
    public String title() { return "Tax Invoice"; }    // protected → public: wider, OK

    @Override
    Invoice copy() { return new Invoice(); }           // covariant: Invoice is a Document

    @Override
    void print() { System.out.println("printing invoice"); }   // throws nothing: narrower, OK
}

public class Main {
    public static void main(String[] args) throws Exception {
        Invoice inv = new Invoice();
        System.out.println(inv.title());
        Invoice copy = inv.copy();                     // no cast needed
        System.out.println(copy.getClass().getSimpleName());
        Document d = inv;
        d.print();                                     // the Invoice version runs
    }
}
Tax Invoice Invoice printing invoice

The compile errors you'll meet

Mistake in the childjavac says
@Override on fees() (typo) or a different parameter typemethod does not override or implement a method from a supertype
Parent public double fee(), child double fee()attempting to assign weaker access privileges; was public
Parent returns double, child returns intreturn type int is not compatible with double
Parent throws IOException, child throws Exceptionoverridden method does not throw Exception
Parent method is finaloverridden method is final
Parent method is static, child's is not (or the reverse)overridden method is static / overriding method is static

The silent bug @Override catches: public boolean equals(Account other) looks like it overrides Object.equals, but the parameter type differs, so it's an overload. Collections like HashSet call equals(Object) and never use yours. With @Override on it, javac reports the mistake immediately. Put @Override on every overriding method.

4 Step by step

Writing an override correctly

  1. Start from the parent's method

    class Payment {
        protected final double amount;
        Payment(double amount) { this.amount = amount; }
        double fee() { return amount * 0.02; }        // default: 2%
    }
    The parent's version is the default. Any child that doesn't override fee() simply inherits it.
  2. Copy the signature exactly, add @Override

    class UpiPayment extends Payment {
        UpiPayment(double amount) { super(amount); }
    
        @Override
        double fee() { return 0; }
    }
    @Override generates no code. It asks javac to check that a matching parent method exists, turning silent mistakes into compile errors.
  3. Keep or widen access, keep or narrow the return type

    A caller holding a Payment variable must be able to call fee() and use the result as a double. So the child may not hide the method behind narrower access or return something incompatible.

    These rules exist so that a child object can be used anywhere a parent object is expected without surprises (the Liskov substitution principle).
  4. Don't add checked exceptions

    A caller of Payment.fee() handles only the exceptions Payment.fee() declares. If an override could throw a new checked exception, that caller would be unprepared.

    The same reasoning: substitutability. Unchecked exceptions (RuntimeException and its children) aren't checked by javac, so they're allowed (Module 9).
  5. Let the JVM choose at run time

    Payment p = new UpiPayment(1000);
    System.out.println(p.fee());    // 0.0, not 20.0
    javac compiles p.fee() as an invokevirtual instruction. At run time the JVM looks at the object's class (UpiPayment) and runs the most specific fee() it finds there, walking up to parents only if needed. Lesson 7.5 goes deeper.
5 See it happen

The object decides which fee() runs: both views

Left: the call and its result. Right: the variable, the object, and where the JVM finds the method.

Overriding fee() 6 steps
What you see Payment p = new Payment(1000); Payment p = new CardPayment(1000); Payment p = new UpiPayment(1000); @Override double fees() { ... } Payment p = new UpiPayment(500); for (Payment p : payments) ... p.fee() → 20.0 p.fee() → 18.0 p.fee() → 0.0 p.info() → Payment UpiPayment.info() → UPI CardPayment 18.0 UpiPayment 0.0 WalletPayment 20.0 error: method does not override or implement a method from a supertype The parent's default: 2% of 1000. Same variable type, different object: 1.8%. UPI's own version: zero fee. A typo in the name is caught at compile time. Static methods follow the variable, not the object. One loop, each object answers for itself. myphatshala.in
Behind the screen Stack Heap Classes (methods) p : Payment #1 Payment #2 Card… #3 Upi… amount 1000.0amount 500.0 Payment fee() 2% static info() CardPaymentfee() 1.8% UpiPaymentfee() 0 static info() WalletPayment(none) static: chosen by the variable's type fees() ≠ fee() no parent method named fees() → javac stops; no class file [0] Card → 1.8% ✓ [1] Upi → 0 ✓ [2] Wallet → up → 2% Object's class is Payment: use Payment.fee(). Object's class is CardPayment: its fee() wins. Variable type is ignored for instance methods. Checked by the compiler, before any object exists. Static calls are fixed at compile time. Not found in the class? The JVM walks up. myphatshala.in
6 Real-world use

Overriding in real systems

Overriding is how frameworks let your code plug in: they call a method, and your class's version runs.

Watch it with an example: month-end interest at a bank

One month-end job calls a.interest() on every account. Savings and fixed deposits override the method; a current account doesn't. Watch which version runs for each object.

One call, the account's own interest() 6 steps
Animated example Account a = new Savings(100000); a.interest(); a = new FixedDeposit(100000); a = new Current(100000); @Override double intrest() {…} total += a.interest(); // each a : Account → Savings → Savings → FixedDeposit → Current → each account Account interest() → 0 Savings 3.5% of balance FixedDeposit 7% of balance Current (no override) 3500.0 7000.0 0.0 inherited from Account error: method does not override or implement a method from a supertype 3500 + 7000 + 0 = 10500.0 The variable's type is Account; the object is Savings. Savings overrides interest(): 100000 × 3.5 / 100. Same call, new object: FixedDeposit's version. Current has no override, so the JVM walks up. A typo with @Override never reaches the bank. One loop pays every account the right interest. myphatshala.in
@OverridetoString() {"Order#812 Rs 499"

1. toString, equals and hashCode

Almost every domain class overrides toString() for readable logs, and equals()/hashCode() so collections compare by value (Lesson 10.3).

log.info(order)→order.toString()→Order#812 Rs 499
UPICard

2. Payment and pricing rules

Checkout code calls method.fee(amount) or offer.discount(cart). Each payment method or offer class overrides the calculation; checkout never changes when a new one is added.

Checkout→payment.fee()→right fee per method
class Worker extends Thread { run() { … }}

3. Thread's run() and other callbacks

Extending Thread and overriding run() is the classic way to give a thread its job (Module 12). Many libraries work like this: they call a method you override.

thread.start()→your run()→work on a new thread
real SMS senderFakeSender (test)

4. Test doubles

In tests, a fake class extends the real SMS or email sender and overrides send() to record the message instead of contacting a gateway. The code under test can't tell the difference.

OrderService→sender.send()→fake records it

Examples are simplified. Many modern codebases implement interfaces (Lesson 7.7) rather than extend classes for these plug-in points, but the dispatch mechanism is the same.

7 Run the code

Fees, the overriding way

class Payment {
    protected final double amount;

    Payment(double amount) {
        this.amount = amount;
    }

    double fee() {
        return amount * 0.02;
    }
}

class CardPayment extends Payment {
    CardPayment(double amount) { super(amount); }

    @Override
    double fee() {
        return amount * 0.018;
    }
}

class UpiPayment extends Payment {
    UpiPayment(double amount) { super(amount); }

    @Override
    double fee() {
        return 0;
    }
}

class WalletPayment extends Payment {
    WalletPayment(double amount) { super(amount); }
}

public class Main {
    public static void main(String[] args) {
        Payment[] payments = {
            new CardPayment(1000), new UpiPayment(1000), new WalletPayment(1000)
        };
        for (Payment p : payments) {
            System.out.println(p.getClass().getSimpleName() + " fee: Rs " + p.fee());
        }
    }
}
CardPayment fee: Rs 18.0 UpiPayment fee: Rs 0.0 WalletPayment fee: Rs 20.0
  • double fee() in PaymentThe default rule; WalletPayment inherits it unchanged.
  • @Override double fee()Same signature and return type; javac confirms a parent method exists.
  • Payment pThe loop only knows "a Payment". The object decides which fee() runs.
  • getClass().getSimpleName()Shows the object's real class at run time.
8 Line by line

One variable, two objects, two fee() versions

Watch the frame label: the same line p.fee() runs Payment.fee first and Upi.fee the second time.

Worked examples: real problems, solved and traced in memory

Three business problems solved with overriding. Run each solution, then step through it: the frame label shows which class's version of a method is actually running, so you can watch the JVM pick the override at run time.

Example 1Fintech · checkout

One loop, the right fee for every payment

Problem statement

A checkout page lists the convenience fee for each payment option on a Rs 1,000 order. Card charges 1.8%, UPI charges nothing, and any other method uses the default 2%.

Input
order amount Rs 1000 (a field); options: Card, UPI, Wallet
Expected output
18.0
0.0
20.0

Approach

  1. Put the default rule in Payment.fee().
  2. Card and Upi override fee(); Wallet doesn't.
  3. Loop over a Payment[] and call fee(); watch which version's frame appears.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2Banking · savings rules

A withdraw override that adds a minimum-balance rule

Problem statement

Every account allows a withdrawal if the balance covers it. A savings account must also keep Rs 1,000 in the account. Override withdraw in Savings so it checks its extra rule and then reuses the parent's logic.

Input
Savings balance 5000; withdraw 4500, then 3000
Expected output
false
true
2000.0

Approach

  1. Account.withdraw checks the balance and debits.
  2. Savings.withdraw first checks the minimum-balance rule.
  3. If that passes, it calls super.withdraw(amount) instead of repeating the debit code.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3Exports · GST invoices

Static label vs overridden rate

Problem statement

Domestic invoices charge 18% GST; export invoices are zero-rated. Each class also has a static label(). Through a variable of type Invoice holding an export invoice, which label and which rate do you get?

Input
Invoice inv = new ExportInvoice(); amount Rs 50000
Expected output
GST invoice, tax 0.0

Approach

  1. rate() is an instance method: overridden, chosen by the object.
  2. label() is static: hidden, chosen by the variable's type at compile time.
  3. Trace both calls and compare their frames: the static call has no this.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 7.4 in one screen

  • Overriding: same signature as an inherited instance method; the child's version replaces it.
  • Return: same or a subtype (covariant). Access: same or wider. Checked exceptions: same, fewer or narrower.
  • Always write @Override: javac then catches typos and wrong parameter types.
  • final methods can't be overridden; private ones aren't inherited.
  • static methods are hidden, chosen by the variable's type at compile time.
  • Instance methods are dispatched at run time by the object's class (dynamic dispatch).
  • Use super.m() inside the override to reuse the parent's version.

Interview and viva questions

What is the difference between overloading and overriding?

Overloading: same method name, different parameter lists, usually in the same class; javac picks the version at compile time from the argument types. Overriding: same signature in a subclass; the JVM picks the version at run time from the object's class. Overloading is compile-time polymorphism, overriding is runtime polymorphism (Lesson 7.5).

Can we override a static method?

No. A static method with the same signature in the child hides the parent's. Which one runs is decided at compile time by the reference type, not the object, so there's no runtime polymorphism. Also, a static method can't hide an instance method or vice versa: javac reports an error.

Why can't an overriding method have weaker access or throw broader checked exceptions?

Because a child object must be usable wherever the parent is expected. Code calling payment.fee() through a parent reference relies on the parent's promises: that the method is accessible and throws only the declared checked exceptions. Narrower access or new exceptions would break those callers.

Can we override the main method or a constructor?

Constructors aren't inherited, so they can't be overridden. main is static, so a main in a child class hides the parent's rather than overriding it; the JVM runs whichever class you launch.

Is @Override mandatory?

No, overriding works without it. But it's strongly recommended: it documents intent and makes javac verify that a matching parent method exists, catching mistakes like equals(Account) instead of equals(Object) or a misspelt name.

Practice problems

In each problem, decide which method the parent provides as a default and which children must override it. Put @Override on every override. The last three are business problems.

P1Easy

Animal sounds

Animal has sound() returning "...". Cow and Cat override it. Loop over an Animal[] with one of each (including a plain Animal) and print the sounds.

Input
Animal, Cow, Cat
Expected output
Animal: ...
Cow: Moo
Cat: Meow
Show solution and explanation
class Animal {
    String sound() {
        return "...";
    }
}

class Cow extends Animal {
    @Override
    String sound() {
        return "Moo";
    }
}

class Cat extends Animal {
    @Override
    String sound() {
        return "Meow";
    }
}

public class Main {
    public static void main(String[] args) {
        Animal[] animals = { new Animal(), new Cow(), new Cat() };
        for (Animal a : animals) {
            System.out.println(a.getClass().getSimpleName() + ": " + a.sound());
        }
    }
}
Animal: ... Cow: Moo Cat: Meow

How it works: every element is used through an Animal variable, yet each object runs its own sound(). The plain Animal falls back to the parent's default.

P2Easy

Override toString for readable output

Printing a Book object shows something like Book@1b6d3586. Override toString() so System.out.println(book) prints the title and price.

Input
"Wings of Fire", Rs 299
Expected output
Wings of Fire (Rs 299.0)
In cart: Wings of Fire (Rs 299.0)
Show solution and explanation
class Book {
    private final String title;
    private final double price;

    Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    @Override
    public String toString() {
        return title + " (Rs " + price + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Book b = new Book("Wings of Fire", 299);
        System.out.println(b);
        System.out.println("In cart: " + b);
    }
}
Wings of Fire (Rs 299.0) In cart: Wings of Fire (Rs 299.0)

How it works: println and string concatenation call toString(), inherited from Object. Our override replaces it. It must be public, because Object's version is public and access can't be narrowed.

P3Easy

Extend, don't replace

Report.header() returns the company name. SalesReport overrides it to return the parent's header followed by " - Sales". Use super.

Input
company "Shree Traders"
Expected output
Shree Traders - Sales
Shree Traders
Show solution and explanation
class Report {
    String header() {
        return "Shree Traders";
    }
}

class SalesReport extends Report {
    @Override
    String header() {
        return super.header() + " - Sales";
    }
}

public class Main {
    public static void main(String[] args) {
        Report r = new SalesReport();
        System.out.println(r.header());
        System.out.println(new Report().header());
    }
}
Shree Traders - Sales Shree Traders

How it works: the override reuses the parent's text with super.header(). Through the Report variable r, the SalesReport version still runs, because the object decides.

P4Easy

Covariant return

Shape.copy() returns a Shape. In Circle, override it to return a Circle, so callers can use radius without a cast.

Input
Circle radius 7
Expected output
original 7.0, copy 10.0
Show solution and explanation
class Shape {
    Shape copy() {
        return new Shape();
    }
}

class Circle extends Shape {
    double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    Circle copy() {
        return new Circle(radius);
    }
}

public class Main {
    public static void main(String[] args) {
        Circle c = new Circle(7);
        Circle c2 = c.copy();
        c2.radius = 10;
        System.out.println("original " + c.radius + ", copy " + c2.radius);
    }
}
original 7.0, copy 10.0

How it works: the override returns Circle, a subtype of Shape: a covariant return, allowed since Java 5. Code holding a Circle gets a Circle back, with no cast. Changing the copy doesn't affect the original because copy() creates a new object.

P5Medium

Static hiding vs overriding

Both Vehicle and Bus have a static category() and an instance maxSpeed(). Call both through a Vehicle variable that holds a Bus, and through a Bus variable. Predict first.

Input
Vehicle v = new Bus(); Bus b = new Bus();
Expected output
v: Vehicle, 80
b: Bus, 80
Show solution and explanation
class Vehicle {
    static String category() { return "Vehicle"; }
    int maxSpeed() { return 100; }
}

class Bus extends Vehicle {
    static String category() { return "Bus"; }

    @Override
    int maxSpeed() { return 80; }
}

public class Main {
    public static void main(String[] args) {
        Vehicle v = new Bus();
        Bus b = new Bus();
        System.out.println("v: " + v.category() + ", " + v.maxSpeed());
        System.out.println("b: " + b.category() + ", " + b.maxSpeed());
    }
}
v: Vehicle, 80 b: Bus, 80

How it works: maxSpeed() is an instance method, so the object (a Bus) decides: 80 both times. category() is static, so javac decides from the variable's type: "Vehicle" through v, "Bus" through b. Prefer Vehicle.category() to make static calls obvious.

P6Medium

Widen access while overriding

Sensor.read() is protected. A TemperatureSensor overrides it as public so that any class can call it. Show it works, and note which direction is allowed.

Input
temperature reading 31.5
Expected output
Temperature: 31.5 C
Via Sensor: 31.5
Show solution and explanation
class Sensor {
    protected double read() {
        return 0;
    }
}

class TemperatureSensor extends Sensor {
    @Override
    public double read() {
        return 31.5;
    }
}

public class Main {
    public static void main(String[] args) {
        TemperatureSensor t = new TemperatureSensor();
        System.out.println("Temperature: " + t.read() + " C");
        Sensor s = t;
        System.out.println("Via Sensor: " + s.read());
    }
}
Temperature: 31.5 C Via Sensor: 31.5

How it works: protected → public makes the method more accessible, which is allowed. The opposite (public → protected) would be "attempting to assign weaker access privileges", because callers using the parent type might lose access.

P7Medium

Fix the equals overload

Two Customer objects with the same mobile number should be equal, including inside a HashSet. Write a correct equals(Object) and hashCode() with @Override, and show the set keeps only one.

Input
Customer("98200 12345"), Customer("98200 12345")
Expected output
Customers in set: 1
Show solution and explanation
import java.util.HashSet;
import java.util.Set;

class Customer {
    private final String mobile;

    Customer(String mobile) {
        this.mobile = mobile;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Customer)) return false;
        Customer other = (Customer) o;
        return mobile.equals(other.mobile);
    }

    @Override
    public int hashCode() {
        return mobile.hashCode();
    }
}

public class Main {
    public static void main(String[] args) {
        Set<Customer> set = new HashSet<>();
        set.add(new Customer("98200 12345"));
        set.add(new Customer("98200 12345"));
        System.out.println("Customers in set: " + set.size());
    }
}
Customers in set: 1

How it works: the parameter type must be Object to override Object's equals; @Override guarantees we got it right. HashSet first uses hashCode() to find the bucket, then equals to compare, so both must be overridden together (Lesson 10.3, 11.6).

P8Business problem

Shapes for a design tool

A poster-design tool needs the total ink area of all shapes on a canvas. Shape.area() returns 0; Rect, Circle and Triangle override it. Print each area (2 decimals) and the total.

Input
Rect 40×25, Circle r=10, Triangle base 30 height 12
Expected output
Rect      1000.00 sq cm
Circle     314.16 sq cm
Triangle   180.00 sq cm
Total     1494.16 sq cm
Show solution and explanation
class Shape {
    double area() {
        return 0;
    }

    String name() {
        return getClass().getSimpleName();
    }
}

class Rect extends Shape {
    private final double w, h;
    Rect(double w, double h) { this.w = w; this.h = h; }

    @Override
    double area() { return w * h; }
}

class Circle extends Shape {
    private final double r;
    Circle(double r) { this.r = r; }

    @Override
    double area() { return Math.PI * r * r; }
}

class Triangle extends Shape {
    private final double base, height;
    Triangle(double base, double height) { this.base = base; this.height = height; }

    @Override
    double area() { return 0.5 * base * height; }
}

public class Main {
    public static void main(String[] args) {
        Shape[] canvas = { new Rect(40, 25), new Circle(10), new Triangle(30, 12) };
        double total = 0;
        for (Shape s : canvas) {
            System.out.printf("%-8s %8.2f sq cm%n", s.name(), s.area());
            total += s.area();
        }
        System.out.printf("Total    %8.2f sq cm%n", total);
    }
}
Rect 1000.00 sq cm Circle 314.16 sq cm Triangle 180.00 sq cm Total 1494.16 sq cm

How it works:

  1. The canvas loop knows only Shape; each object supplies its own formula through overriding.
  2. name() is inherited unchanged; getClass() returns the object's real class, so it prints Rect, Circle, Triangle.
  3. Adding a Hexagon means one new class; the total-area code stays the same. (In Lesson 7.6 Shape becomes abstract, since a plain Shape has no real area.)
P9Business problem

Diwali bonus by employee type

Everyone gets a Diwali bonus of 8.33% of basic (one month's pay per year, roughly). Managers get the normal bonus plus Rs 10,000; interns get a flat Rs 3,000. Override bonus() and print a bonus sheet.

Input
Employee "Anil" 30000, Manager "Kiran" 70000, Intern "Zoya" 15000
Expected output
Anil (Employee): Rs 2499.0
Kiran (Manager): Rs 15831.0
Zoya (Intern): Rs 3000.0
Total bonus payout: Rs 21330.0
Show solution and explanation
class Employee {
    protected final String name;
    protected final double basic;

    Employee(String name, double basic) {
        this.name = name;
        this.basic = basic;
    }

    double bonus() {
        return Math.round(basic * 0.0833);
    }
}

class Manager extends Employee {
    Manager(String name, double basic) { super(name, basic); }

    @Override
    double bonus() {
        return super.bonus() + 10000;
    }
}

class Intern extends Employee {
    Intern(String name, double basic) { super(name, basic); }

    @Override
    double bonus() {
        return 3000;
    }
}

public class Main {
    public static void main(String[] args) {
        Employee[] staff = {
            new Employee("Anil", 30000), new Manager("Kiran", 70000), new Intern("Zoya", 15000)
        };
        double total = 0;
        for (Employee e : staff) {
            System.out.println(e.name + " (" + e.getClass().getSimpleName() + "): Rs " + e.bonus());
            total += e.bonus();
        }
        System.out.println("Total bonus payout: Rs " + total);
    }
}
Anil (Employee): Rs 2499.0 Kiran (Manager): Rs 15831.0 Zoya (Intern): Rs 3000.0 Total bonus payout: Rs 21330.0

How it works:

  1. Anil: 30000 × 0.0833 = 2499. Kiran: 70000 × 0.0833 = 5831 (rounded), + 10,000 = 15,831. Zoya: flat 3,000.
  2. Manager extends the rule with super.bonus(); Intern replaces it.
  3. The payroll loop has no if-else on job type.
P10Business problem

Delivery time estimate by vehicle

A delivery app estimates minutes for a trip: DeliveryVehicle.eta(km) assumes 30 km/h plus 10 minutes of handling. A Bike is faster in traffic (40 km/h); a Drone flies at 60 km/h with only 2 minutes of handling but can't go beyond 10 km (return -1). Print ETAs for 6 km and 15 km.

Input
distances 6 km and 15 km
Expected output
6.0 km:  DeliveryVehicle=22 min  Bike=19 min  Drone=8 min
15.0 km:  DeliveryVehicle=40 min  Bike=33 min  Drone=n/a
Show solution and explanation
class DeliveryVehicle {
    int eta(double km) {
        return (int) Math.ceil(km / 30 * 60) + 10;
    }
}

class Bike extends DeliveryVehicle {
    @Override
    int eta(double km) {
        return (int) Math.ceil(km / 40 * 60) + 10;
    }
}

class Drone extends DeliveryVehicle {
    @Override
    int eta(double km) {
        if (km > 10) return -1;
        return (int) Math.ceil(km / 60 * 60) + 2;
    }
}

public class Main {
    public static void main(String[] args) {
        DeliveryVehicle[] fleet = { new DeliveryVehicle(), new Bike(), new Drone() };
        double[] trips = {6, 15};
        for (double km : trips) {
            System.out.print(km + " km:");
            for (DeliveryVehicle v : fleet) {
                int m = v.eta(km);
                System.out.print("  " + v.getClass().getSimpleName() + "=" + (m < 0 ? "n/a" : m + " min"));
            }
            System.out.println();
        }
    }
}
6.0 km: DeliveryVehicle=22 min Bike=19 min Drone=8 min 15.0 km: DeliveryVehicle=40 min Bike=33 min Drone=n/a

How it works:

  1. Default vehicle: 6 km at 30 km/h = 12 min + 10 = 22. Bike: 9 + 10 = 19. Drone: 6 + 2 = 8.
  2. For 15 km the drone's override returns −1, which the caller shows as n/a.
  3. The dispatcher code that picks the fastest vehicle would only call eta(); each vehicle class owns its own speed rules.

Real-world scenario questions (asked in company interviews)

Overriding bugs often compile and run, then quietly do the wrong thing. Decide your answer first, then open the model answer.

E-commerce · cart1–3 years

Duplicate products in a HashSet

A Product class has public boolean equals(Product p) comparing SKUs, and also a correct hashCode(). A HashSet<Product> still keeps two products with the same SKU. The equals method passes its unit test. Explain.

The interviewer is checking: do you know overloading vs overriding and why @Override matters?

Show a strong answer
  • equals(Product) overloads Object.equals(Object). HashSet calls equals(Object), which is still Object's identity comparison.
  • The unit test called a.equals(b) with both declared as Product, so javac picked the overload; the test proved nothing about collections.
  • Fix: @Override public boolean equals(Object o), check the type, cast, compare SKUs. With @Override, the original mistake wouldn't have compiled.

Avoid: adding more tests with Product-typed variables only. Test through the collection.

Payments · new providerFresher

Adding a payment method without touching checkout

Checkout currently has a switch on payment type to compute fees and refund rules. The business wants to add "Buy Now Pay Later". How would you restructure the code so adding the next method is easy?

The interviewer is checking: can you replace type checks with overriding?

Show a strong answer
  • Create a parent Payment with fee() and refundDays() (default rules), and one subclass per method that overrides what differs.
  • Checkout calls payment.fee() without knowing the type; dynamic dispatch picks the right version.
  • BNPL becomes one new class with its own overrides; existing classes and checkout don't change (the open/closed principle).

Avoid: adding one more case to every switch. That's how Riya's net banking bug happened.

Banking · library upgrade1–3 years

The override that stopped being called

Your PremiumAccount overrides calculateInterest(double rate) from a vendor library's Account. After upgrading the library, premium customers get normal interest. The vendor changed the parent method to calculateInterest(BigDecimal rate). Your code had no @Override. What happened, and what would have caught it?

The interviewer is checking: do you understand how signature changes silently break overriding?

Show a strong answer
  • Your method's signature no longer matches the parent's, so it became an unrelated method. The library calls its own calculateInterest(BigDecimal), which you don't override.
  • With @Override, the upgrade would have failed to compile with "method does not override or implement a method from a supertype", pointing straight at the problem.
  • Fix: update the override's signature, add @Override everywhere, and add a test that checks premium interest through the library's entry point.

Avoid: treating @Override as optional decoration. It's a safety check.

SaaS · utilities3+ years

"I overrode the static formatter but nothing changed"

A shared DateUtil class has static String format(LocalDate d). A team writes IndiaDateUtil extends DateUtil with its own static format (dd-MM-yyyy) and passes an IndiaDateUtil object wherever a DateUtil is expected. Reports still show the old format. Why, and what design would work?

The interviewer is checking: do you know that static methods don't take part in dynamic dispatch?

Show a strong answer
  • Static methods are hidden, not overridden. Calls compiled against DateUtil always run DateUtil.format; the object passed in is ignored.
  • For swappable behaviour, use instance methods: an interface DateFormatter with implementations, injected where needed (Lesson 7.7).
  • Static utility methods are fine for fixed behaviour; once you need variations, make it an object.

Avoid: calling static methods through instance variables. It compiles but suggests polymorphism that doesn't exist.