Skip to content

10 · 32 · 5 min

Two words one letter apart

They sound alike, mean opposite things, and get confused constantly.

Overloading: same name, different parameters

java
void print(int n)      { }
void print(String s)   { }
void print(int n, int m) { }

Which one runs is decided at compile time from the declared types of the arguments. The methods are unrelated except for sharing a name, and it is really a naming convenience.

Overriding: same signature, different class

java
class Animal { String speak() { return "..."; } }
class Dog extends Animal {
    @Override String speak() { return "woof"; }
}

Which one runs is decided at run time from the actual object. This is what makes polymorphism work.

OverloadingOverriding
Signaturemust differmust match
Relationshipsame class, or inheritedsubclass replaces parent
Resolvedcompile timerun time
Calledstatic bindingdynamic dispatch

The trap

java
class Base {
    void handle(Object o) { System.out.println("object"); }
}
class Derived extends Base {
    void handle(String s) { System.out.println("string"); }
}

Base b = new Derived();
b.handle("hello");        // prints "object"

Derived did not override anything. A different parameter type makes it an overload, so Base still has only its Object version, and the declared type of b is Base, so that is what is called.

This is exactly what @Override prevents. Annotating the method would have failed to compile with "method does not override", and that is why the annotation is worth writing every time even though it is optional.

Overloading with null and numbers

java
print(null);       // ambiguous if both String and Integer versions exist

Overload resolution with autoboxing, varargs and widening is genuinely complicated, and overloads that differ only in ways the compiler must reason hard about are best avoided. Two clearly named methods beat two overloads that require a language lawyer.

In languages without it

Python has no overloading: a later definition simply replaces an earlier one. The idiomatic equivalent is default arguments or inspecting the type inside a single function. Overriding works normally, because it is about the object rather than about signatures.