A mobile recharge app lets you recharge with just a number, with a number and an amount, or with a number and a plan name. All three are "recharge", so it would be silly to name them recharge1, recharge2 and recharge3. The same app sends alerts by SMS, email and WhatsApp. Each channel "sends" in its own way, but the code that triggers an alert should not care which one it is talking to. The first situation is method overloading; the second is method overriding. The names sound alike, but they solve different problems and work in completely different ways.

Overloading = same method name, DIFFERENT parameters, usually in the same class. The compiler picks the version at compile time.
Overriding = a child class redefines a parent method with the SAME signature. The actual object picks the version at runtime.

Real-world analogy: At a railway counter, "book a ticket" works with just a destination, with a destination and a date, or with a destination, a date and a class. Same request, different details: that is overloading. Now think of "deliver a parcel": India Post, a courier and a drone service all deliver, each in its own way. You hand over the parcel and the actual service decides how it travels: that is overriding.

1. Method Overloading: Same Name, Different Parameters

Method overloading means defining several methods with the same name but different parameter lists. The parameter lists must differ in at least one of these ways:

  • Number of parameters: recharge(number) vs recharge(number, amount).
  • Types of parameters: recharge(String number, int amount) vs recharge(String number, String planName).
  • Order of parameter types: book(String, int) vs book(int, String) (legal, but usually confusing).

The method name plus its parameter types is called the method's signature. Overloads must have different signatures.

Output

9876543210: Rs.239 (default plan)
9876543210: Rs.199
9876543210: Rs.299 (UNLIMITED plan)
9876543210: Rs.149
9123456780: Rs.149

From the caller's side, recharge is one operation that accepts different inputs. From the compiler's side, these are four separate methods. Notice that overload 1 simply calls overload 2 with a default amount. This "delegate to the fuller version" style keeps the logic in one place.

What does NOT count as a different signature

  • Return type alone. int total() and double total() cannot exist together. When you write total(); and ignore the result, the compiler would have no way to choose.
  • Parameter names. recharge(String number) and recharge(String mobile) are the same signature.
  • Access modifiers, static or throws clauses. None of these create a new signature.

Overloading in Python and JavaScript

Python and JavaScript do not support overloading. A second method with the same name simply replaces the first. They get the same flexibility from default parameters, optional parameters and type checks (isinstance, typeof, Array.isArray), as the code above shows. Python also offers functools.singledispatchmethod to choose an implementation by the type of the first argument. C++ supports overloading just like Java.

2. How the Compiler Picks an Overload

Overloading is resolved at compile time (also called static binding or compile-time polymorphism). The compiler looks at the types of the arguments and finds the best match. Java does this in three phases, and stops at the first phase that finds a match:

  1. Exact match or widening (for example int to long or double), without boxing.
  2. Autoboxing and unboxing (for example int to Integer).
  3. Varargs (String... numbers).

If two methods match equally well, the call is ambiguous and the code does not compile:

void price(int a, double b) { }
void price(double a, int b) { }

price(10, 20);           // error: reference to price is ambiguous
price(10, (double) 20);  // fixed: the cast makes the first version the only match

An important consequence: overload choice uses the declared type of the argument, not the actual object. If Object o = "hello";, then print(o) calls print(Object), even if a print(String) exists. Overriding works the other way round, as you will see next.

3. Method Overriding: Same Signature, Different Behavior

Method overriding happens when a child class defines a method with the same signature as a method it inherits, and replaces the parent's behavior for objects of that child class. Which version runs is decided at runtime by the actual object (dynamic binding or runtime polymorphism).

Output

[SMS] to ravi: Your mock test re...
[EMAIL] to ravi@example.com: Your mock test result is out: 82/100
[WHATSAPP] to ravi: Your mock test result is out: 82/100 (read receipt on)
to ravi: Your mock test result is out: 82/100

Things to notice:

  • The loop calls n.send(...) on a variable of type Notifier, yet each object runs its own send().
  • An override can reuse the parent version with super.send(...) (Java, Python, JavaScript) or Notifier::send(...) (C++), and add to it.
  • InAppNotifier does not override anything, so it uses the inherited Notifier.send().
  • In C++, the parent method must be virtual; without it, the call through a Notifier pointer would always run the parent version.

Rules for a valid override (Java and C++)

  • Same name and same parameter list.
  • Same return type, or a subtype (a covariant return type). A parent returning Notifier can be overridden by a child returning SmsNotifier.
  • Access can stay the same or become wider, never narrower. A public method cannot become private in the child.
  • No new or broader checked exceptions (Java).
  • static, private and final methods cannot be overridden. A static method with the same signature in the child hides the parent's version; a private method is not inherited at all; a final method cannot be overridden (C++ also has final).

4. The Classic Trap: An "Override" That Is Not One

The most common bug is a child method that looks like an override but does not match the parent's signature. In Java and C++, a different parameter list turns it into an overload (or, in C++, a new function that hides the parent's), and the parent version silently keeps running. In Python and JavaScript, which have no overloading, the usual version of this bug is a typo in the method name.

Output

PushNotifier:      Notifier.send -> Exam starts at 10 AM
FixedPushNotifier: FixedPushNotifier.send -> Exam starts at 10 AM

PushNotifier never replaced send(text), so the parent version ran. The fix is to let the compiler check your intent:

  • Java: put @Override on every method meant to override. If nothing is overridden, compilation fails with "method does not override or implement a method from a supertype".
  • C++: write override after the parameter list. With warnings on, g++ also reports that Notifier::send "was hidden" by the new function.
  • Python 3.12+: decorate with @typing.override so type checkers such as mypy and pyright flag mistakes.
  • TypeScript: use the override keyword together with the noImplicitOverride compiler option.

5. Overloading vs Overriding: Side by Side

AspectOverloadingOverriding
PurposeSeveral ways to call the same operationA child changes how an inherited operation works
WhereUsually the same class (overloads can also be inherited)Parent class and child class
Method nameSameSame
ParametersMust be differentMust be the same
Return typeCan be different (but cannot be the only difference)Same or a subtype (covariant)
Access modifierCan be anythingSame or wider
DecidedAt compile time, from the declared argument typesAt runtime, from the actual object
Also calledStatic binding, compile-time polymorphismDynamic binding, runtime polymorphism
Inheritance needed?NoYes
static / private / final methodsCan be overloadedCannot be overridden
Compiler checkAmbiguous calls are rejected@Override / override
Python / JavaScriptNot supported; use default or optional parametersFully supported

6. Practical Example: A Payment Gateway Using Both

Real designs often use both techniques together. A PaymentGateway parent offers an overloaded pay(): with or without a coupon. The shared steps (applying the coupon, adding the fee, formatting the result) live once in the parent. Each gateway overrides only what is different: its fee and how it charges the money.

Output

UPI | pay(500) | fee Rs.0 | collect request sent for Rs.500
Card | pay(500) | fee Rs.10 | card charged Rs.510
Card | pay(500, SAVE50) | fee Rs.9 | card charged Rs.459
Wallet | pay(500, SAVE50) | fee Rs.0 | wallet debited Rs.450, balance Rs.550
Wallet | pay(300) | fee Rs.0 | wallet debited Rs.300, balance Rs.250

Why This Design Works

  • Overloading makes the API convenient. Callers write pay(500) or pay(500, "SAVE50"), and the one-argument version just forwards to the two-argument version.
  • Overriding keeps the differences local. The card fee lives in CardGateway, the wallet balance lives in WalletGateway. Adding a "Net Banking" gateway means adding one class, without touching pay().
  • pay() is final in Java, so no gateway can accidentally change the shared payment steps. Only the hook methods fee() and charge() are meant to be overridden. This structure is the Template Method pattern.
  • UPI and Wallet do not override fee(), so they inherit the default fee of 0.

7. When to Use Which

  • Use overloading when one operation naturally accepts different inputs: a constructor with optional fields, add(item) and add(item, quantity), or log(String) and log(Exception). All overloads should do the same job.
  • Use overriding when different types of objects must do the same operation in their own way: notifiers, payment methods, shapes, pricing rules. It is how you replace if (type == ...) chains with polymorphism.
  • Avoid too many overloads. Five overloads of create() with similar parameter types are hard to read and easy to call wrongly. Consider a Builder or a parameter object instead.

8. Common Mistakes

  • Trying to overload by return type only. It does not compile.
  • Changing the parameter list in a child and expecting an override. You get an overload (or a hidden method in C++), and the parent version keeps running. Always use @Override / override.
  • Forgetting virtual in C++. Without it there is no runtime dispatch, and calls through a base pointer run the base version.
  • "Overriding" a static method. The child's static method only hides the parent's; which one runs depends on the declared type, not the object.
  • Expecting overloads to be chosen by the runtime type. print(Object o) is chosen for an Object variable even when it holds a String.
  • Defining the same method name twice in Python or JavaScript to "overload" it. Only the last definition survives.
  • Overloads that do different jobs. If recharge(String) recharges and recharge(int) cancels a plan, they deserve different names.

Summary

  • Overloading: same name, different parameters, chosen by the compiler from the argument types. Supported in Java and C++; Python and JavaScript use default and optional parameters instead.
  • Overriding: same signature in a child class, chosen at runtime by the actual object. Supported in all four languages (C++ needs virtual).
  • Return type alone or parameter names alone never make a new overload.
  • An override must keep the parameter list, may return a subtype, may widen access but never narrow it, and cannot override static, private or final methods.
  • Mark every override with @Override, override or @typing.override so the compiler or type checker catches mistakes.
  • In LLD, overloading makes an API convenient to call, and overriding makes a design easy to extend. Good designs, like the payment gateway, use both.