Back to Polymorphism
Q1Question 1 of 7

What is the difference between overloading and overriding?

Key Concepts
  • Overloading: same method name, different parameter lists, within the same class, resolved at compile time
  • Overriding: same method name and parameter list, in a child class replacing the parent implementation, resolved at runtime
  • The compiler picks the overloaded method based on argument types (static dispatch)
  • The JVM picks the overridden method based on the actual object type in the heap (dynamic dispatch via vtable)
  • Overloading changes the method signature (parameters); overriding preserves the signature but changes the behavior
  • You cannot overload by only changing the return type: the compiler reports ambiguous method

Definition

Overloading means same name with different parameters, resolved at compile time. Overriding means same name with the same parameters in a child class, resolved at runtime. Overloading changes the form; overriding changes the behavior.

Two forms of polymorphism exist in Java.

  • Method overloading (compile-time polymorphism): same method name, different parameter lists (number, type, or order), within the same class or inherited class. The compiler picks the matching signature based on argument types. This is static dispatch.
  • Method overriding (runtime polymorphism): same method name and parameter list in a child class that replaces the parent implementation. The JVM picks the correct version at runtime based on the actual object type in the heap. This is dynamic dispatch via the virtual method table (vtable).
  • Overloading changes the signature (parameters). Overriding preserves the signature but changes the behavior.
  • Overloading resolution order when no exact match: widening (int to long), then autoboxing (int to Integer), then varargs (int...).

Overloading (compile-time): National Bank of India has three deposit slips: one for cash (deposit with BigDecimal), one for cheque (deposit with BigDecimal and cheque number), one for electronic transfer (deposit with BigDecimal, reference, and channel). Same name, different forms. The clerk (compiler) looks at what you hand in and picks the right form before you reach the counter. Overriding (runtime): the interest department calls calculateInterest() on each account. SavingsAccount returns 4% of balance. CurrentAccount returns zero. The call center (JVM) routes to the correct handler based on the actual account type at the moment of the call.

Understanding the Concepts

What is the difference between overloading and overriding?

Overloading and overriding are both forms of polymorphism but resolved at different times and by different mechanisms.

What is method overloading?

Overloading means multiple methods in the same class share the same name but differ in their parameter lists. The compiler resolves which method to call at compile time by matching argument types to the most specific signature.

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class FeeCalculator {

    // Overload 1: fee for a trade amount
    public BigDecimal calculateFee(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.001"));  // 0.1% brokerage
    }

    // Overload 2: fee for amount + fee type
    public BigDecimal calculateFee(BigDecimal amount, String feeType) {
        return switch (feeType) {
            case "BROKERAGE" -> amount.multiply(new BigDecimal("0.001"));
            case "GST" -> calculateFee(amount).multiply(new BigDecimal("0.18"));
            default -> BigDecimal.ZERO;
        };
    }

    // Overload 3: fee for amount + fee type + intraday flag
    public BigDecimal calculateFee(BigDecimal amount, String feeType, boolean isIntraday) {
        BigDecimal base = calculateFee(amount, feeType);
        return isIntraday ? base.multiply(new BigDecimal("0.5")) : base;
    }
}

// Compile-time resolution:
// calc.calculateFee(new BigDecimal("100000"));                     -> overload 1
// calc.calculateFee(new BigDecimal("100000"), "BROKERAGE");        -> overload 2
// calc.calculateFee(new BigDecimal("100000"), "BROKERAGE", true);  -> overload 3

What are the rules for overloading?

Overloading must change the parameter list. It can optionally change the return type, access modifier, or exceptions. It cannot overload by only changing the return type.

JAVA
// Valid overloads: different parameter lists
BigDecimal calculateFee(BigDecimal amount)
BigDecimal calculateFee(BigDecimal amount, String type)
BigDecimal calculateFee(int quantity, BigDecimal price)

// INVALID: same params, different return type only
// BigDecimal calculateFee(BigDecimal amount)
// String calculateFee(BigDecimal amount)  // compile error: ambiguous

What is method overriding?

Overriding means a child class provides a specific implementation for a method already defined in its parent. The JVM resolves which method to call at runtime by checking the actual object type in the heap.

JAVA
package com.bank.core;

import java.math.BigDecimal;

public class Account {
    public BigDecimal calculateInterest() {
        return BigDecimal.ZERO;
    }
}

public class SavingsAccount extends Account {
    private BigDecimal rate = new BigDecimal("0.04");

    @Override
    public BigDecimal calculateInterest() {
        return getBalance().multiply(rate);
    }
}

public class CurrentAccount extends Account {
    @Override
    public BigDecimal calculateInterest() {
        return BigDecimal.ZERO;
    }
}

// Runtime resolution:
Account a1 = new SavingsAccount("SAV2001", new BigDecimal("100000"));
Account a2 = new CurrentAccount("CUR3001", new BigDecimal("500000"));
a1.calculateInterest();  // JVM: actual type = SavingsAccount -> 4000
a2.calculateInterest();  // JVM: actual type = CurrentAccount -> 0

What are the rules for overriding?

Overriding must preserve the method name and parameter list. The return type can be the same or a covariant subtype. The access modifier can be the same or wider. Checked exceptions can be the same or narrower. Final, static, and private methods cannot be overridden.

JAVA
// Covariant return type: parent returns Account, child can return SavingsAccount
class AccountFactory {
    Account create(String id) { return new Account(id); }
}
class SavingsFactory extends AccountFactory {
    @Override
    SavingsAccount create(String id) { return new SavingsAccount(id); } // covariant
}

// Access: can widen (protected -> public), cannot narrow (public -> private)
// Exceptions: can narrow (IOException -> FileNotFoundException), cannot widen

How does overloading compare to overriding?

| Aspect | Overloading | Overriding |
|--------|------------|------------|
| Parameters | Must be different | Must be the same |
| Class scope | Same class or inherited | Child class replaces parent |
| Resolution time | Compile time | Runtime |
| Resolution by | Compiler (argument types) | JVM (actual object type, vtable) |
| Polymorphism type | Static (compile-time) | Dynamic (runtime) |
| @Override needed? | No | Yes (strongly recommended) |
| Return type | Can differ | Same or covariant subtype |
| Access modifier | Can differ | Same or wider |

How does the JVM resolve each internally?

Overloading is resolved at compile time. The compiler embeds the specific method signature in the bytecode. Overriding is resolved at runtime through the vtable (virtual method table).

JAVA
// Overloading bytecode:
// invokevirtual FeeCalculator.calculateFee(Ljava/math/BigDecimal;)
// The signature is baked at compile time.

// Overriding bytecode:
// invokevirtual Account.calculateInterest()Ljava/math/BigDecimal;
// At runtime: JVM checks actual object's vtable.
// SavingsAccount vtable: calculateInterest -> SavingsAccount.calculateInterest
// CurrentAccount vtable: calculateInterest -> CurrentAccount.calculateInterest

Why is @Override important?

Without @Override, a typo creates a new method silently instead of overriding the parent.

JAVA
// WRONG: typo, no @Override
public class SavingsAccount extends Account {
    public BigDecimal calculateIntrest() {  // typo: "Intrest" not "Interest"
        return getBalance().multiply(rate);
    }
    // This is a NEW method. Account.calculateInterest() still returns 0. Silent bug.
}

// CORRECT: @Override catches the typo
public class SavingsAccount extends Account {
    @Override
    public BigDecimal calculateIntrest() {  // compile error:
        // "method does not override or implement a method from a supertype"
    }
}

What should a senior engineer know?

  • The JVM uses invokevirtual for instance method calls and looks up the vtable at runtime. This is why overriding works.
  • The JVM uses invokestatic for static method calls, binding at compile time. This is why static methods cannot be overridden (only hidden).
  • HotSpot can inline final methods and devirtualize monomorphic call sites (only one implementation exists) for performance.
  • In generics, overloading with type-erased signatures can cause compile errors because List and List both erase to List.

Visual Overview

DIAGRAM
  OVERLOADING (compile-time)
  ┌─────────────────────────────────────┐
  │  FeeCalculator                       │
  │  calculateFee(BigDecimal)            │
  │  calculateFee(BigDecimal, String)    │
  │  calculateFee(BigDecimal, String, boolean) │
  │                                      │
  │  Compiler checks argument types      │
  │         │                            │
  │         ▼                            │
  │  Picks specific signature at         │
  │  COMPILE TIME                        │
  └─────────────────────────────────────┘

  OVERRIDING (runtime)
  ┌─────────────────────────────────────┐
  │  Account ref = new SavingsAccount()  │
  │  ref.calculateInterest()             │
  │         │                            │
  │         ▼                            │
  │  JVM checks ACTUAL object type       │
  │  in the heap at RUNTIME              │
  │         │                            │
  │         ▼                            │
  │  SavingsAccount vtable               │
  │  calculateInterest -> SavingsAccount │
  │  version                             │
  └─────────────────────────────────────┘

Key Takeaway

Overloading and overriding are both forms of polymorphism but resolved at different times. Overloading: same method name with different parameter lists in the same class. The compiler resolves it at compile time by matching argument types to method signatures. calculateFee(amount), calculateFee(amount, type), calculateFee(amount, type, intraday) are three signatures, one name, and the compiler picks based on arguments. This is compile-time polymorphism or static dispatch.

Overriding: same method name with the same parameter list in a child class. The JVM resolves it at runtime by looking at the actual object type in the heap, not the variable type in the code. Account account = new SavingsAccount(...) has variable type Account, but account.calculateInterest() calls SavingsAccount's version because the JVM dispatches to the actual type via the vtable. This is runtime polymorphism or dynamic dispatch.

In Pankaj's banking platform, overloading is proven by the same FeeCalculator object responding differently to different argument counts, with the compiler baking the specific signature into bytecode. Overriding is proven by different Account objects (SavingsAccount: 4000, CurrentAccount: 0) responding differently to the same calculateInterest() call, with the JVM dispatching based on actual type at runtime.

Remember one sentence: the compiler picks the overloaded method by argument types at compile time, and the JVM picks the overridden method by actual object type at runtime.

---

Share
Use ← → arrow keys to navigate