ehavioursWhat Is Polymorphism?

Polymorphism literally means “many forms”.
In object‑oriented programming, it means that one interface or function call can work with objects of different types, and each type can respond in its own way.

Examples in plain English:

  • You call draw() on a Shape, and a Circle draws a circle while a Rectangle draws a rectangle.

  • You call send() on a Notifier, and an email notifier sends an email while an SMS notifier sends a text.

Key idea:

  • The caller only knows about the common interface.

  • The actual object type determines what happens at run time.

In Low Level Design, polymorphism lets you design flexible code where new behaviours can be added without changing the existing calling code.

Two Main Flavors: Compile‑Time vs Run‑Time

You will see two broad forms of polymorphism:

  1. Compile‑time polymorphism (overloading)

    • Same function name, different parameter lists, resolved by the compiler.

    • Example: multiple print methods with different parameters.

    • Not the main focus in LLD discussions, but good to know.

  2. Run‑time polymorphism (overriding)

    • Same function name in a base class and derived classes.

    • Which implementation runs depends on the actual object at runtime.

    • Implemented in C++ using virtual functions and base‑class references/pointers.

For LLD, when people say “polymorphism”, they almost always mean run‑time polymorphism via overriding.

Compile‑Time Polymorphism (Method Overloading) – Quick Look

In C++, you can define multiple functions with the same name but different parameter lists:


The compiler decides which log to call based on the arguments:


Same method name, different forms.
This is compile‑time polymorphism because the choice is made by the compiler, not at runtime.

Run‑Time Polymorphism (Method Overriding) – Core for LLD

Run‑time polymorphism relies on:

  • A base class with at least one virtual method.

  • Derived classes that override that method.

  • Using a base‑class pointer or reference to call the method.

General pattern:


Using polymorphism:


The same function process works with any class derived from Base.
Which implementation runs depends on the actual object passed in.

This is the heart of polymorphism in LLD.

Example: Notification Polymorphism

Consider an LLD scenario where the system can notify users in different ways.

Common Interface


Different Implementation:


Code Using Polymorphism


Here:

  • sendOrderUpdate depends only on the Notifier abstraction.

  • You can pass any notifier implementation.

  • The same call notifier.send(...) behaves differently depending on the actual object type.

This is polymorphism used exactly the way LLD problems expect: a clean interface, multiple implementations.

Why Polymorphism Is So Useful in LLD

Polymorphism gives several advantages when designing systems:

  • One interface, many implementations

    • Your high‑level code depends on abstractions, not concrete classes.

    • You can plug in new behaviors without changing the caller.

  • Extensibility

    • To add a new payment method, notification channel, or storage type, you just add a new derived class.

    • Existing code that uses the base interface does not need modification.

  • Cleaner code

    • Instead of big if/else chains or switch statements scattered everywhere, you encapsulate behavior in derived classes.

    • Code that chooses “which implementation to use” is localized.

  • Testability

    • You can pass mock or fake implementations in tests, using the same interface.

In interviews, showing polymorphic design (one abstraction with several implementations) is a strong signal of good design thinking.

LLD‑Style Example: Pricing Strategy

Imagine a ride‑hailing app where rides can be priced in different ways: normal pricing, surge pricing, or discount pricing.

Strategy Interface


Different Strategies


Using the Strategy Polymorphically


The Ride class depends only on the abstract PricingStrategy.
You can add new strategies (e.g., festival pricing, loyalty pricing) later, and Ride does not need to change.

This is a classic polymorphic design that shows up in many LLD problems.

Guidelines for Using Polymorphism

Polymorphism is powerful, but should be used intentionally.

Good situations to use polymorphism:

  • You have a clear abstraction (e.g., Notifier, PaymentMethod, Storage, PricingStrategy).

  • You expect multiple implementations, either now or in the future.

  • You want high‑level code to stay the same while low‑level behavior changes.

Things to be careful about:

  • Do not create inheritance hierarchies “just because”.

  • Prefer a clear interface or abstract base with focused responsibilities.

  • Avoid making hierarchies too deep or complicated.

  • Consider composition first if the relationship is not “is‑a”.

In LLD interviews, a simple, well‑designed interface with a couple of concrete implementations is usually better than an overcomplicated hierarchy.

Summary

Polymorphism is the ability to treat different types through a common interface and let each type provide its own behavior.
In C++ and LLD, this is mainly achieved using inheritance, abstract classes, and virtual functions.

You saw:

  • What polymorphism means and how it differs from simple inheritance.

  • The difference between compile‑time (overloading) and run‑time (overriding) polymorphism.

  • How to implement run‑time polymorphism in C++ using virtual functions and base‑class references or pointers.

  • Practical LLD‑style examples with notifiers and pricing strategies.

  • When polymorphism is useful and what design guidelines to keep in mind.