skip to content

Compile-time vs Runtime Polymorphism

Overloading is chosen by the compiler from the declared types, overriding is chosen by the JVM from the runtime type. Being able to name which mechanism applies to a given call is the point of most Java polymorphism questions.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

4

What is the difference between method overloading and method overriding in Java, and which kind of polymorphism does each represent?

level: juniorimportance: must knowfreq 88%

answer

  1. Overload = same name, different params, COMPILE time
  2. Override = same signature, chosen at RUNTIME by real object
  3. Return type is not part of the signature
  4. @Override catches accidental overloads
  5. Static type picks overload, dynamic type picks override

basics

~20 s

Overloading means several methods share a name but take different parameters; the compiler picks one. Overriding means a subclass replaces a parent method with the same signature; Java picks it at runtime based on the real object. Overloading is compile-time polymorphism, overriding is runtime polymorphism.

solid answer

~50 s

Method overloading is having multiple methods with the same name but different parameter lists (number, types, or order) in the same class or hierarchy. The compiler decides which overload to call using the static (declared) types of the arguments, so it is resolved at compile time, which is why it is called compile-time or static polymorphism. Method overriding is when a subclass provides its own implementation of a method it inherits, keeping the exact same name and parameter list (signature). The decision of which override to run is made at runtime based on the actual object's class, not the reference type, so it is runtime or dynamic polymorphism. Overloading varies the signature and resolves statically; overriding keeps the signature and resolves dynamically. Return type alone cannot distinguish overloads, but a covariant return type is allowed when overriding.

code

java · 16 lines
java
class Animal {
    String sound() { return "..."; }
}
class Dog extends Animal {
    @Override String sound() { return "Woof"; }   // OVERRIDING: same signature, runtime dispatch
}

class Calc {
    int add(int a, int b)       { return a + b; }      // OVERLOADING:
    double add(double a, double b) { return a + b; }    // same name, different params,
    int add(int a, int b, int c){ return a + b + c; }  // resolved at compile time
}

Animal a = new Dog();
a.sound();          // -> "Woof"  (chosen from the real object: runtime)
new Calc().add(2, 3); // -> compiler picks add(int,int) from arg types (compile time)

go deeper

for a junior

Can define both terms with a simple example: overloading = same name different parameters; overriding = subclass redefines a parent method. Knows overloading is compile-time, overriding is runtime.

for a middle

Explains resolution clearly: overloading uses the static/declared argument types at compile time; overriding uses the object's runtime type. Knows the return-type rule and the purpose of @Override and covariant returns.

for a senior

Articulates the static-vs-dynamic-type distinction precisely, knows the overload resolution phases (exact/widening/boxing/varargs), and the override constraints (access, covariance, static hiding, field hiding).

for a principal

Frames it in terms of binding time and design impact: overloads as a thin API convenience vs overriding as the substitutability mechanism enabling polymorphic design (LSP, programming to interfaces); aware of overload-ambiguity pitfalls in API design.

## Setting the stage: what is polymorphism? **Polymorphism** (Greek: "many forms") means a single name or interface can refer to different concrete behaviors. In Java this shows up in two distinct mechanisms that beginners often confuse: **overloading** and **overriding**. They look superficially similar (two methods with the same name) but they are resolved at completely different times and for completely different reasons. ### Key term: method signature A method's **signature** is its name plus the ordered list of its parameter types. For example `area(int)` and `area(int, int)` and `area(double)` are three different signatures. **The return type is NOT part of the signature.** This single fact explains many of the rules below. ### Key term: static (declared) type vs dynamic (runtime) type When you write `Animal a = new Dog();`, the variable `a` has: - a **static type** (also called declared or compile-time type) of `Animal` — what the compiler sees from the declaration, and - a **dynamic type** (runtime type) of `Dog` — the actual class of the object that exists in memory at runtime. The whole overloading-vs-overriding distinction comes down to *which of these two types is used to choose the method*. ## Method overloading = compile-time (static) polymorphism **Overloading** means defining several methods with the **same name but different signatures** (different parameter count, types, or order). They are independent methods that merely share a name for readability. ```java 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; } ``` When you call `add(2, 3)`, the **compiler** looks at the *static types* of the arguments (`int`, `int`) and picks the matching overload **at compile time**. The chosen method is effectively baked into the bytecode. Because the decision is made before the program ever runs, this is called **compile-time**, **static**, or **early binding** polymorphism. Rules and gotchas: - Overloads must differ in their parameter list. **You cannot overload by return type alone** — `int f()` and `double f()` would be ambiguous because nothing at the call site tells the compiler which to pick. - Java applies a resolution order: it first tries an exact match, then **widening** (e.g. `int`→`long`→`double`), then **autoboxing** (`int`→`Integer`), then **varargs**. This can produce surprising choices. - Overloading can happen in one class or across a parent/child class. ## Method overriding = runtime (dynamic) polymorphism **Overriding** means a subclass supplies its own body for an inherited method using the **exact same signature**. ```java class Animal { String sound() { return "..."; } } class Dog extends Animal { @Override String sound() { return "Woof"; } } class Cat extends Animal { @Override String sound() { return "Meow"; } } Animal a = new Dog(); a.sound(); // "Woof" ``` Here the compiler only knows `a` is an `Animal`, so it cannot decide which `sound()` to run. The decision is deferred to **runtime**, where the JVM inspects the *dynamic type* of the object (`Dog`) and dispatches to `Dog.sound()`. This is called **runtime**, **dynamic**, or **late binding** polymorphism, and the mechanism that performs it is **dynamic method dispatch** (covered in its own question). Rules and gotchas: - The signature must match exactly; if it differs you have accidentally *overloaded*, not overridden — this is why the `@Override` annotation exists: it makes the compiler verify you really are overriding. - The access modifier cannot be more restrictive than the parent's. - The return type must be the same or a **covariant** (subtype) return. - `static`, `private`, and `final` methods cannot be overridden (statics are *hidden*, not overridden — they bind statically). - Fields are never overridden; field access is always resolved by static type (this is *field hiding*). ## Side-by-side summary | | Overloading | Overriding | |---|---|---| | Signature | must DIFFER | must be IDENTICAL | | Resolved using | static type of arguments | dynamic type of object | | Resolved when | compile time | runtime | | Other names | static / early binding | dynamic / late binding | | Across classes? | same class or hierarchy | strictly parent → child | ## Deriving the answer At any level you can reconstruct the right answer from one principle: **overloading varies the signature and is chosen by the compiler from declared types; overriding keeps the signature and is chosen by the JVM from the real object.** Everything else (return-type rule, `@Override`, covariance, statics) follows from that.

  • Can two methods be overloaded if they differ only by return type?
    No. The return type is not part of the signature, so the call site would be ambiguous and it will not compile. The parameter list must differ.
  • What does the @Override annotation actually do?
    It tells the compiler 'I intend to override a supertype method'; the compiler then verifies a matching method exists. If you mistyped the signature (accidentally overloading), compilation fails, catching the bug early.

saying these in an interview costs you the question

  • Claiming you can overload methods by changing only the return type
  • Saying overriding is decided by the reference (declared) type instead of the actual object
  • Confusing the two: calling overloading 'runtime polymorphism'
  • Thinking static or private methods can be overridden (they are hidden)
  • Believing fields are polymorphic like methods

context

open as a page

How does the JVM resolve an overridden method call at runtime? Explain dynamic method dispatch.

level: middleimportance: must knowfreq 74%

basics

~20 s

Each object knows its real class. When you call an overridden method through a parent-type reference, the JVM looks up the method on the object's actual class at runtime and runs that version. This runtime lookup is called dynamic method dispatch.

open as a page

At compile time, how does Java choose among overloaded methods, and why can overloading combined with overriding produce surprising results?

level: seniorimportance: should knowfreq 52%

basics

~20 s

The compiler picks an overload using the declared (static) types of the arguments, in phases: exact match, then widening, then autoboxing, then varargs. Because this uses declared types, a value's real runtime type does not change which overload is selected, which can surprise people.

open as a page

Why is runtime polymorphism the foundation for extensible object-oriented design, and what does it cost?

level: principalimportance: should knowfreq 40%

basics

~20 s

Runtime polymorphism lets you write code against a general type and add new subtypes later without changing that code. The caller stays the same while behavior varies by the real object. The cost is indirection, harder static reasoning, and some performance overhead, mostly optimized away by the JIT.

open as a page