skip to content

Polymorphism (Dispatch & Binding)

How Java decides which method body actually runs: overloading resolved at compile time versus overriding dispatched at runtime, plus casting between related types. Interviewers build output-prediction puzzles from exactly this distinction.

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

questions

13

What is the difference between upcasting and downcasting in Java, and which one is implicit?

level: juniorimportance: must knowfreq 72%

answer

  1. Up = to parent, implicit, always safe
  2. Down = to child, explicit (Type)x, can throw
  3. ClassCastException at runtime, not compile time
  4. Guard a downcast with instanceof
  5. Upcast hides subtype-only members

basics

~20 s

Upcasting treats an object as one of its parent types (Dog seen as Animal); it is automatic and always safe. Downcasting treats it back as the more specific child type (Animal back to Dog); it needs an explicit cast and can fail at runtime.

solid answer

~40 s

Upcasting converts a reference from a subtype to a supertype, e.g. assigning a Dog to an Animal variable. It is implicit (no cast operator needed) and always safe, because every Dog IS-A Animal. The trade-off is that through the supertype reference you can only call members declared on the supertype. Downcasting goes the other way, narrowing a supertype reference back to a subtype, e.g. (Dog) animal. It requires an explicit cast because the compiler cannot prove the object really is a Dog; it only checks the types are related. At runtime the JVM verifies the object's actual class, and if it is not a Dog it throws ClassCastException. So the rule of thumb: upcast freely, downcast only when you know (or check with instanceof) the real runtime type.

code

java · 9 lines
java
Animal a = new Dog();      // upcast: implicit, safe
// a.fetch();              // won't compile: fetch() is Dog-only

if (a instanceof Dog d) {  // guarded, pattern-matching downcast
    d.fetch();             // safe — d is the Dog
}

Animal cat = new Cat();
Dog bad = (Dog) cat;       // compiles, throws ClassCastException at runtime

go deeper

for a junior

Knows upcast is automatic/safe and downcast needs (Type) and can fail; can write the basic cast syntax.

for a middle

Explains the static-vs-runtime-type distinction, that ClassCastException is a runtime error, and guards downcasts with instanceof.

for a senior

Distinguishes compile-time impossibility (unrelated types) from runtime ClassCastException (related but wrong), uses pattern-matching instanceof, and treats frequent downcasting as a design smell to refactor toward polymorphism.

for a principal

Frames casting in terms of API design: minimizing client downcasts via well-factored type hierarchies, sealed types + pattern switches for closed sets, generics to avoid casts, and the cost/safety trade-offs of escape hatches.

## Background: reference types and the type hierarchy In Java, an object lives on the heap and is reached through a **reference variable**. The variable has a *declared type* (also called its **static type** or *compile-time type*) — what the compiler thinks it is — and the object it points to has an *actual type* (its **runtime type** or *dynamic type*) — the real class it was created with via `new`. Classes form an inheritance tree. If `class Dog extends Animal`, then `Dog` is a **subtype** of `Animal`, and `Animal` is the **supertype**. The IS-A relationship reads downward-to-upward: a Dog IS-A Animal, but an Animal is not necessarily a Dog. ## Upcasting **Upcasting** is converting (assigning) a reference of a subtype to a variable of a supertype: ```java Dog d = new Dog(); Animal a = d; // upcast — implicit, no operator ``` It is **implicit**: you write no cast operator, because it is provably safe. Every Dog object genuinely *is* an Animal, so no runtime check is needed. Upcasting never throws. The cost is **narrowing of the visible interface**: through the `Animal` reference `a`, the compiler only lets you call members *declared on `Animal`*. A `Dog`-only method like `fetch()` is invisible, even though the object can still do it. (Overridden methods still dispatch to the Dog version at runtime — that is polymorphism — but the *compiler* checks against the declared type.) ## Downcasting **Downcasting** is the reverse: narrowing a supertype reference back to a subtype. It requires an **explicit cast operator**: ```java Animal a = new Dog(); Dog d = (Dog) a; // downcast — explicit d.fetch(); // now Dog-only members are visible ``` The compiler permits the cast only if the types are *related* (one could be the other). It cannot prove the object truly is a `Dog`, so it inserts a **runtime type check**. At execution the JVM compares the object's actual class against `Dog`: - if compatible, the cast succeeds; - if not (e.g. the object is really a `Cat`), it throws **`ClassCastException`**. ```java Animal a = new Cat(); Dog d = (Dog) a; // compiles fine, throws ClassCastException at runtime ``` Casting to a completely **unrelated** type (e.g. `(String) a` where neither is the other) is a *compile-time* error instead — the compiler knows it can never work. ## Why downcasting is risky and how to guard it Downcasting is the point where the compiler's safety net has a hole: it trusts your assertion about the runtime type. The standard guard is the **`instanceof`** operator, which tests the actual type *before* casting: ```java if (a instanceof Dog) { Dog d = (Dog) a; d.fetch(); } ``` Modern Java (16+) folds the test and cast into one **pattern-matching `instanceof`**: ```java if (a instanceof Dog d) { // tests AND binds d in one step d.fetch(); } ``` Note `null instanceof Dog` is always `false`, and casting `null` to any reference type is always allowed (a null reference has no runtime type to check), so `(Dog) null` never throws. ## Mental model - **Upcast** = generalize, implicit, always safe, hides specifics. - **Downcast** = specialize, explicit, may throw `ClassCastException`, reveals specifics. - Frequent downcasting is often a **design smell** — it suggests you've thrown away type information you should have kept, or that polymorphism (an overridden method) would express the intent better.

  • After upcasting a Dog to an Animal, why can you still get polymorphic Dog behavior from an overridden method?
    Because instance-method calls use dynamic dispatch: the compiler checks the call against the declared (Animal) type, but at runtime the JVM invokes the override on the object's actual class (Dog). Casting changes the visible interface, not the object.
  • What does (Dog) null do?
    Nothing harmful — casting null to any reference type is always allowed and never throws, because null has no runtime type to verify.

saying these in an interview costs you the question

  • Saying downcasting is checked at compile time — the failure is a runtime ClassCastException.
  • Claiming upcasting needs a cast operator — it is implicit.
  • Thinking upcasting can fail — it never throws.
  • Confusing reference casting with primitive casting (int/double); they are unrelated mechanisms.

context

open as a page

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%

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.

open as a page

What is the difference between static (early) binding and dynamic (late) binding in Java, and when does each happen?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Binding is how a call is matched to the actual method or variable. Static binding is decided at compile time (overloaded methods, static methods, fields). Dynamic binding is decided at run time based on the real object type, which is how overridden instance methods work.

open as a page

How do you safely downcast a reference, and what does the instanceof operator give you (including pattern matching)?

level: middleimportance: must knowfreq 68%

basics

~20 s

Before casting a parent-typed reference to a child type, check it with instanceof. If the check passes, the cast is safe. Modern Java lets you do both at once: if (x instanceof Dog d) binds d so you skip the separate cast.

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

Which kinds of members in Java are statically bound and which are dynamically bound? Walk through each case.

level: middleimportance: must knowfreq 60%

basics

~20 s

Dynamically bound (resolved at run time by the object's real type): ordinary overridable instance methods. Statically bound (resolved at compile time by the declared type): fields, static methods, private methods, and final methods. Overload selection is also done at compile time.

open as a page

When does a Java cast fail at compile time versus throwing ClassCastException at runtime?

level: middleimportance: should knowfreq 55%

basics

~20 s

If two types are completely unrelated, the compiler rejects the cast immediately. If they are related (one could be the other) the cast compiles, but if the actual object is the wrong one it throws ClassCastException when the code runs.

open as a page

Predict the output: with `Parent p = new Child();`, where Child overrides a method and shadows a same-named field, what does calling the method vs reading the field return, and why?

level: middleimportance: should knowfreq 45%

basics

~20 s

The method call returns Child's version because instance methods are dynamically bound to the object's real type (Child). The field read returns Parent's value because fields are statically bound to the reference's declared type (Parent). Methods follow the object; fields follow the reference.

open as a page

Does casting a reference change the object or its behavior? Explain how casting interacts with field access versus overridden methods.

level: seniorimportance: should knowfreq 48%

basics

~20 s

Casting never changes the object on the heap; it only changes what type the compiler thinks the reference has, which controls which members are visible. Overridden method calls always run the object's real version, but field access uses the reference's declared type.

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

How does the JVM implement dynamic dispatch (virtual vs non-virtual invocation), and how does the JIT optimize it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Virtual method calls (invokevirtual/invokeinterface) look up the right override at run time, usually through a per-class method table. Non-virtual calls (invokestatic/invokespecial) have a fixed target. The JIT often removes the lookup by inlining when a call site sees only one or two types.

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

How do generics and type erasure change the meaning and safety of casts in Java, and when is an unchecked-cast suppression justified?

level: principalimportance: nice to knowfreq 33%

basics

~20 s

Generic type arguments vanish at runtime (erasure), so a cast like (List<String>) can only really check that it's a List, not that the elements are Strings. The compiler warns 'unchecked' and the wrong type may blow up later as a ClassCastException.

open as a page