Have you ever gotten confused trying to understand OOP polymorphism, from overriding to protocols, all at once?
Overloading, overriding, and protocols can look similar, so they’re easy to mix up.
Here’s the conclusion first: polymorphism means that “a call with the same name behaves differently depending on its target.” Overriding implements this through inheritance, while protocols implement it through a contract.
This article walks through how these three concepts fit together, step by step.
What exactly is polymorphism?
Polymorphism comes from the Greek words “poly” (many) and “morph” (forms).
Simply put, it is the ability of different objects to respond differently to the same message.
For example, tell a dog and a cat to “make a sound,” and the dog barks while the cat meows.
The caller only needs to invoke 소리내(). Each object decides what to produce.
The key idea of polymorphism is that “the caller doesn’t need to know.”
You can handle whatever arrives with the same code.
This makes code much more flexible. Adding a new animal does not require changing the calling code.
Overloading vs. overriding: what’s the difference?
Their similar names are where most confusion starts.
Overloading means defining multiple functions with the same name but different parameters.
Overriding means that a child redefines and replaces a function provided by its parent.
It’s easier to see with code, so let’s look at an example.
class Animal {
func sound() { print("...") }
}
class Dog: Animal {
override func sound() { print("Bark") } // Overriding
}
let a: Animal = Dog()
a.sound()
// Output: Bark
The interesting part is that even though a has type Animal, it produces Dog’s sound.
That is polymorphism decided at runtime by looking at the actual object.
Overloading is slightly different. The function to call is chosen at compile time.
func add(_ a: Int, _ b: Int) -> Int { a + b }
func add(_ a: String, _ b: String) -> String { a + b }
print(add(1, 2)) // Output: 3
print(add("a", "b")) // Output: ab
That’s why overloading is also called static polymorphism and overriding dynamic polymorphism.
Why do we need protocols?
Inheritance can provide polymorphism, so why do protocols exist?
Inheritance represents an “is-a” relationship, so a type is limited to one parent. It’s difficult for a cat to be an animal while also belonging to another hierarchy.
A protocol is much more flexible: anything that follows the contract can conform.
protocol Soundable {
func sound()
}
struct Cat: Soundable {
func sound() { print("Meow") }
}
struct Car: Soundable {
func sound() { print("Honk") }
}
let things: [Soundable] = [Cat(), Car()]
things.forEach { $0.sound() }
// Output: Meow
// Output: Honk
A cat and a car have no inheritance relationship, yet they can be stored in the same array and handled identically.
Polymorphism without an inheritance hierarchy—that is the power of protocols.
It is also why languages such as Swift favor protocols over inheritance.
When should you use inheritance or protocols?
Here are the criteria I use in practice.
| Situation | Decision |
|---|---|
| Want to inherit shared code | Inheritance (overriding) |
| No relationship, but want to guarantee the same behavior | Protocol |
| Want to assign multiple capabilities at once | Protocol (multiple conformances allowed) |
| Want to give a value type (struct) polymorphism | Protocol |
You only need to remember a few simple rules.
- Use inheritance when you want to reuse a lot of the parent’s implementation.
- Use a protocol when you want to define an “ability.”
- If extensibility is a concern, consider protocols first.
This is how it comes up in interviews
Q. Explain the difference between overloading and overriding.
Overloading defines different parameters under the same name and is decided at compile time. Overriding means that a child redefines an inherited method and is decided at runtime based on the actual object. That is why the former is called static polymorphism and the latter dynamic polymorphism.
Q. What are the benefits of using protocols instead of inheritance?
Inheritance is limited to one parent and cannot be used with value types, while a type can conform to multiple protocols and structs can conform as well. Even unrelated types can be grouped and handled under the same contract, which makes extension easier.
Polymorphism may seem abstract at first, but once you hold on to one sentence—“the caller doesn’t need to know”—everything else falls into place.
Try implementing today’s overriding and protocols in code yourself, and they’ll really stick. You’ve got this!

