Low-Level Design Tutorial 0/42 lessons ~6 min read Lesson 9

    Polymorphism

    Polymorphism lets one interface invoke different behaviors depending on runtime type.

    Course progress0%
    Focus
    9 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Polymorphism lets one interface invoke different behaviors depending on runtime type. vehicle.requiredSpotType() works for Car and Truck without conditional logic scattered through ParkingLot — cleaner LLD and easier extension.

    Interviewers explicitly test polymorphism when they ask 'how would you add a motorcycle later?' The answer should be a new class, not editing ten switch statements.

    Dynamic dispatch in Java, combined with interfaces and sealed types, is your primary tool for open-ended design problems.

    Understanding the topic

    Key concepts

    • Compile-time polymorphism: method overloading (same name, different params).
    • Runtime polymorphism: overriding — JVM selects implementation by object type.
    • Interface polymorphism: multiple classes implement same interface.
    • Eliminates large switch/if-else on type codes.
    • Works with collections: List holds heterogeneous types.
    • Pattern Strategy is polymorphism applied to interchangeable algorithms.

    Step-by-step explanation

    1. Define common supertype (interface or abstract class).
    2. Implement behavior in each concrete class.
    3. Client code references supertype only.
    4. Add new types by adding classes, not modifying clients (OCP).
    5. Use polymorphic factories when creation also varies.
    6. Combine with double dispatch only when problem requires it (rare in interviews).

    Informative example

    Pricing polymorphism — no switch on customer type in checkout:

    java
    public interface PricingPolicy {
    Money price(Money base, Customer customer);
    }
    public final class RegularPricing implements PricingPolicy {
    @Override
    public Money price(Money base, Customer customer) { return base; }
    }
    public final class MemberPricing implements PricingPolicy {
    @Override
    public Money price(Money base, Customer customer) {
    return base.multiply(0.9);
    }
    }
    public final class Checkout {
    private final PricingPolicy pricing;
    public Checkout(PricingPolicy pricing) { this.pricing = pricing; }
    public Money total(Money base, Customer customer) {
    return pricing.price(base, customer);
    }
    }

    New student discount = new PricingPolicy implementation; Checkout unchanged — classic OCP + polymorphism.

    Real-world use

    Real-world applications

    • Payment methods, fare calculation, chess piece moves.
    • Export formats (JSON, CSV) from same report object.
    • Plugin-style behavior in extensible modules.

    Best practices

    • Replace switch-on-type with polymorphic methods early.
    • Keep polymorphic sets closed with sealed types when appropriate.
    • Test each concrete behavior independently.
    • Avoid instanceof followed by cast when polymorphism fits.
    • Document extension points for new subtypes.

    Common mistakes

    • instanceof chains growing every sprint.
    • Public switch in one method instead of distributed behavior.
    • Overloading confusion — wrong method picked at compile time.
    • Breaking polymorphism by downcasting to concrete types in clients.

    Advanced interview questions

    Q1BeginnerWhat is polymorphism?
    Same interface, different implementations selected at runtime based on actual object type.
    Q2BeginnerOverloading vs overriding?
    Overloading: same class, different signatures. Overriding: subclass replaces superclass instance method.
    Q3IntermediateHow does polymorphism help LLD interviews?
    New features add classes instead of editing many conditional blocks.
    Q4IntermediateWhen is switch preferable to polymorphism?
    Simple closed enums with trivial behavior and no expected growth.
    Q5AdvancedDesign polymorphic fare calculation for cab types.
    FareCalculator interface per CabType implementation; Trip calls calculator.compute(distance, time); add BikeCabCalculator without changing Trip.

    Summary

    Polymorphism defers behavior to subtypes or interface implementations. Eliminates fragile type-switch logic in clients. Supports Open/Closed Principle for new variants. Combine with dependency injection for testability. Prefer behavior on the object over external switches.

    Ready to mark this lesson complete?Track your journey across the entire course.