Skip to main content

Object oriented programming

[C++] OOPs - Polymorphism

What is Polymorphism in C++? Let’s Discover It Together

Hey there! So you’ve heard this big word—polymorphism—and you’re wondering, “What’s the deal with that?” Don’t worry—we’re going to figure it out together. Let’s not dump a textbook definition right away. Instead, let's have a conversation, explore a fun real-life scenario, and gradually unravel the mystery behind this concept.

Let’s Warm Up with a Real-Life Analogy

Q: Ever been to a restaurant where you say just one word—“Serve”—and magically different things happen based on who hears it?

Let me explain. Imagine we’re in a restaurant. There’s a waiter, a chef, and a cleaner.

  • If you say “Serve” to the waiter, he brings food to your table.
  • If you say “Serve” to the chef, he starts plating the dish.
  • If you say “Serve” to the cleaner, maybe he clears the dishes.

Same command—“Serve”—but different actions depending on who receives it.

That’s polymorphism in essence: “One command, many behaviors.”

Let’s Build a Common Interface (But Stay Generic)

Q: What if we want to model this scenario in C++? How would we go about it?

Let’s begin with a common class for all types of people in the restaurant. We’ll call it Staff. This will have a serve() function, but we’ll keep it super basic for now.

#include <iostream>
using namespace std;
 
// A general Staff class
class Staff {
public:
    // General serve function
    void serve() {
        cout << "Staff is serving in a general way." << endl;
    }
};

What Did We Just Do?
We created a Staff class with a simple function serve(). This is like our remote control with just one button—"Serve". But right now, the action is generic. It's not tailored to a waiter or a chef yet.

So let’s start adding more specific roles.


Create Specialized Classes (Same Function, Different Meaning)

Q: How do we give different staff members their own “serve” behavior?

Easy! Let’s create two new classes—Waiter and Chef—and make them inherit from Staff. Each will have their own version of serve().

// Waiter inherits from Staff
class Waiter : public Staff {
public:
    // Waiter's version of serve
    void serve() {
        cout << "Waiter is serving food to the table." << endl;
    }
};
 
// Chef inherits from Staff
class Chef : public Staff {
public:
    // Chef's version of serve
    void serve() {
        cout << "Chef is plating the dish to be served." << endl;
    }
};

What’s Happening Here?
We now have two specialized versions of the serve() function. The function name is the same, but the behavior is different. That’s the core idea of polymorphismsame name, different forms.


What Happens When We Use a Base Class Pointer?

Let’s get tricky.

Q: What if we have a Staff* pointer, and we point it to a Waiter object? Which version of serve() will get called?

Let’s try it:

int main() {
    // Base class pointer
    Staff* staff;
 
    // Create Waiter and Chef objects
    Waiter waiter;
    Chef chef;
 
    // Point to Waiter
    staff = &waiter;
    staff->serve();  // What gets printed?
 
    // Point to Chef
    staff = &chef;
    staff->serve();  // What gets printed?
 
    return 0;
}

Let’s See the Output

Staff is serving in a general way.
Staff is serving in a general way.

Wait—what? Even though waiter and chef have their own versions of serve(), we’re still getting the base class message!


Why This Happens: Static Binding

Q: Why is the base class function being called?

Because the compiler only knows that staff is of type Staff*. It doesn’t care that it actually points to a Waiter or Chef. So it binds the serve() function at compile-time. This is called static binding or early binding.


Enter Our Hero: The virtual Keyword

Q: How can we tell C++ to wait until runtime before deciding which serve() to call?

We just need to tell the compiler, “Hey! The real behavior depends on the actual object, not the pointer type.” We do this using the virtual keyword.

Let’s update our Staff class:

// Updated Staff class with virtual serve
class Staff {
public:
    // Virtual function allows runtime binding
    virtual void serve() {
        cout << "Staff is serving in a general way." << endl;
    }
};

Now What Happens?
Let’s run the same main() function again. The output now becomes:

Waiter is serving food to the table.
Chef is plating the dish to be served.

Boom! Now it works like magic. This is runtime polymorphism—deciding the function to call at runtime based on the actual object type.


Want More Structure? Let’s Go Abstract

Let’s make this even cleaner. Suppose our base class Staff doesn’t even know how to serve. It just knows that every derived class must implement serve().

Q: How do we make sure derived classes implement a function?
We make the function pure virtual.

// Abstract base class using pure virtual function
class Staff {
public:
    // Pure virtual function - no implementation here
    virtual void serve() = 0;
};

Now let’s update our Waiter and Chef classes accordingly:

// Waiter must define serve
class Waiter : public Staff {
public:
    void serve() {
        cout << "Waiter is serving food to the table." << endl;
    }
};
 
// Chef must define serve
class Chef : public Staff {
public:
    void serve() {
        cout << "Chef is plating the dish to be served." << endl;
    }
};

Important Note
We can no longer do this:

Staff s;  // ❌ Error! Cannot create object of abstract class

But we can do:

Staff* s = new Waiter();  // ✅ Okay!

Because pointers and references to abstract classes are allowed.


Let’s Wrap It All in a Neat Final Example

#include <iostream>
using namespace std;
 
// Abstract base class
class Staff {
public:
    virtual void serve() = 0; // Pure virtual function
};
 
// Derived class: Waiter
class Waiter : public Staff {
public:
    void serve() override {
        cout << "Waiter: Bringing food to the customer." << endl;
    }
};
 
// Derived class: Chef
class Chef : public Staff {
public:
    void serve() override {
        cout << "Chef: Preparing and plating the food." << endl;
    }
};
 
// Derived class: Cleaner
class Cleaner : public Staff {
public:
    void serve() override {
        cout << "Cleaner: Clearing the table after service." << endl;
    }
};
 
int main() {
    // Base class pointer array
    Staff* staffMembers[3];
 
    // Assign derived objects to base class pointers
    staffMembers[0] = new Waiter();
    staffMembers[1] = new Chef();
    staffMembers[2] = new Cleaner();
 
    // Call serve() on each - runtime polymorphism in action
    for (int i = 0; i < 3; ++i) {
        staffMembers[i]->serve();
    }
 
    // Clean up
    for (int i = 0; i < 3; ++i) {
        delete staffMembers[i];
    }
 
    return 0;
}

What Are We Seeing?
One function—serve()—but three very different behaviors based on the actual object. That’s polymorphism. The same interface (Staff*) gives multiple behaviors. Pretty neat, huh?


But Wait! There's Also Compile-Time Polymorphism

Q: Is this the only kind of polymorphism in C++?

Nope. There’s also compile-time polymorphism like:

  • Function Overloading
  • Operator Overloading

Example:

// Function overloading - same name, different parameters
void greet() {
    cout << "Hello!" << endl;
}
 
void greet(string name) {
    cout << "Hello, " << name << "!" << endl;
}

This is resolved at compile-time, hence called compile-time polymorphism.


Quick Interview Q&A Recap

  • What is polymorphism? One interface, many behaviors.
  • Types? Compile-time (overloading), Runtime (virtual functions).
  • What’s a virtual function? A function that can be overridden and resolved at runtime.
  • What’s a pure virtual function? A function declared as = 0—must be overridden.
  • What’s an abstract class? A class with at least one pure virtual function.
  • Can we create objects of abstract classes? Nope. Only pointers or references.
  • What happens if base class method isn't virtual? Static binding—base version is always called.
  • What’s slicing? When a derived object is assigned to a base object and derived parts are lost.

Final Analogy to Remember Forever

Imagine you have a single universal remote. You press the same button—Play—but:

  • On the TV, it starts your show.
  • On the Speaker, it plays music.
  • On the Console, it launches a game.

Same command—different behavior. That’s polymorphism in C++.


Final Learnings

So next time someone asks you, “What’s polymorphism?” just smile and say:
“It’s like one button that means many things—depending on who hears it.”

And if they look confused, tell them about your favourite waiter, chef, and cleaner—they’ll get it.