Solid foundation stones forming the base of a massive architectural structure
Software Engineering Jun 4, 2026 • 15 min read

Overrides, Polymorphism, and the Foundation That Makes OOP Actually Work

Your parent taught you to shake hands. You decided to fist bump instead. That's method overriding. This lesson covers overriding vs overloading, the vtable, abstract classes vs interfaces, and the Template Method pattern that powers every framework you've ever used.

Share:
Lee Foropoulos

Lee Foropoulos

15 min read

Continue where you left off?
Text size:

Contents

Complete Guide | Lesson 10: NormalizationLesson 11: Polymorphism → Lesson 12: Architecture (Jun 11)

Your parent taught you to shake hands. You decided to fist bump instead. Same social contract (greet the person), different implementation. That's method overriding.

You answer the phone differently when it's your boss versus your best friend versus an unknown number. Same action (answer), different behavior based on context. That's polymorphism.

This is Lesson 11, and it maps to Yesod on the Tree of Life. Yesod means "Foundation." It's the sephira that connects everything above (the abstract concepts) to Malkuth below (the physical manifestation). Without Yesod, the ideas never become real. Without polymorphism, interfaces are just paperwork, inheritance is just copying, and design patterns are just theory. Polymorphism is the mechanism that makes all of object-oriented programming actually work at runtime.

Without polymorphism, an interface is a contract nobody enforces. Overriding is the enforcement. It's where your objects stop being blueprints and start being individuals.

Method Overriding: Same Name, Different Behavior

Overriding happens when a child class replaces a parent's method with its own implementation. The method signature (name, parameters, return type) stays the same. The behavior changes.

1CLASS Animal
2    VIRTUAL METHOD speak()
3        RETURN "..."
4    END METHOD
5END CLASS
6
7CLASS Dog EXTENDS Animal
8    OVERRIDE METHOD speak()
9        RETURN "Woof!"
10    END METHOD
11END CLASS
12
13CLASS Cat EXTENDS Animal
14    OVERRIDE METHOD speak()
15        RETURN "Meow."
16    END METHOD
17END CLASS
18
19CLASS Snake EXTENDS Animal
20    OVERRIDE METHOD speak()
21        RETURN "Hssssss."
22    END METHOD
23END CLASS

All four classes have a speak() method. The parent provides a default (silence). Each child replaces it with its own voice. The contract is honored (every Animal can speak). The implementation is individual.

Now the magic:

1animals = [new Dog(), new Cat(), new Snake()]
2
3FOR EACH animal IN animals
4    PRINT animal.speak()
5END FOR
6
7// Output:
8// Woof!
9// Meow.
10// Hssssss.

One loop. One method call. Three different behaviors. The code doesn't ask "what type are you?" It just says "speak" and trusts each object to respond correctly. That's polymorphism in action.

Multiple paths diverging from a single starting point across a landscape
One method call, many behaviors. The calling code doesn't know or care which specific type it's talking to. It sends a message and trusts the contract. Each path leads somewhere different, but they all start from the same point.

The Keywords That Matter

Most languages use explicit keywords to mark overriding:

virtual (C#, C++): marks a parent method as "children may override me." Without this, the method is sealed, and overriding it would be an error or a hidden shadow.

override (C#, C++, Kotlin): explicitly declares "I am replacing my parent's behavior." This isn't just documentation. It's a safety net. If you misspell the method name, the compiler catches it because no parent method matches.

@Override (Java): an annotation that serves the same purpose. Optional but strongly recommended.

Python and JavaScript: no keywords needed. If you define a method with the same name in a child class, it overrides. No compiler checks. This is convenient and dangerous in equal measure.

2 keywords
virtual and override. That's all it takes to unlock runtime polymorphism. The parent permits, the child overrides, and the runtime resolves. Every design pattern in Lesson 8 depends on this mechanism.

Method Overloading: Same Name, Different Parameters

Overloading is NOT overriding. They sound similar but are completely different mechanisms.

Overloading means defining multiple methods with the same name but different parameter lists:

1CLASS Printer
2    METHOD print(text: String)
3        // Print a string
4    END METHOD
5
6    METHOD print(number: Integer)
7        // Print an integer
8    END METHOD
9
10    METHOD print(text: String, copies: Integer)
11        // Print a string multiple times
12    END METHOD
13END CLASS
14
15printer.print("Hello")          // Calls version 1
16printer.print(42)               // Calls version 2
17printer.print("Hello", 3)       // Calls version 3

Three methods, one name, three different parameter signatures. The compiler determines which one to call based on the arguments you pass. This is resolved at compile time, not runtime.

Why overloading exists: convenience. Without it, you'd need printString(), printInteger(), printStringWithCopies(). Overloading lets the method name stay intuitive while handling different input types.

Overriding vs Overloading

Overriding: Same method signature, different class (child replaces parent). Resolved at runtime. The object's actual type determines which version runs. Overloading: Same method name, different parameters, same class. Resolved at compile time. The argument types determine which version runs. Memory trick: Override = parent/child relationship (vertical). Overload = same-class variations (horizontal).

Under the Hood: The vtable

When you call animal.speak() and the runtime figures out whether to bark, meow, or hiss, something has to make that decision. That something is the vtable (virtual method table).

Every class that has virtual methods gets a vtable: a lookup table that maps method names to actual function pointers. Every object carries a hidden pointer to its class's vtable.

1Dog's vtable:
2  speak() → Dog.speak [function at memory address 0x4A20]
3  eat()   → Animal.eat [inherited, address 0x3B10]
4
5Cat's vtable:
6  speak() → Cat.speak [function at memory address 0x5C30]
7  eat()   → Animal.eat [inherited, address 0x3B10]

When you call animal.speak():

  1. Follow the object's vtable pointer
  2. Look up speak() in the table
  3. Call the function at the address stored there

If the object is a Dog, the vtable points to Dog's speak. If it's a Cat, the vtable points to Cat's speak. The calling code doesn't know or care. It just follows the pointer.

Nanoseconds
of overhead per vtable lookup. That's the cost of runtime polymorphism. In exchange, you get code that handles any type without modification. Arguably the best performance trade-off in all of computer science.

This is why polymorphism works: the same method call on different objects routes to different implementations through the vtable. It's not magic. It's a pointer lookup in a table. But the design power it unlocks is extraordinary.

Abstract Classes vs Interfaces (The Final Clarification)

We covered interfaces in Lesson 6. We covered inheritance in Lesson 5. Now let's address the question that confuses everyone: when do you use an abstract class, and when do you use an interface?

Abstract Classes: Shared Code + Enforced Contract

An abstract class is a class that can't be instantiated directly. It exists only to be inherited. It can provide actual implementations (shared code) AND declare abstract methods that children must override.

1ABSTRACT CLASS DataMiner
2    // Shared implementation: every miner does this the same way
3    METHOD mine(path)
4        data = this.extract(path)      // Abstract: each miner extracts differently
5        clean = this.transform(data)    // Abstract: each miner transforms differently
6        this.load(clean)               // Abstract: each miner loads differently
7    END METHOD
8
9    // Children MUST implement these:
10    ABSTRACT METHOD extract(path): RawData
11    ABSTRACT METHOD transform(data): CleanData
12    ABSTRACT METHOD load(data): void
13END CLASS
14
15CLASS CsvMiner EXTENDS DataMiner
16    OVERRIDE METHOD extract(path)
17        RETURN readCsvFile(path)
18    END METHOD
19    OVERRIDE METHOD transform(data)
20        RETURN parseCsvRows(data)
21    END METHOD
22    OVERRIDE METHOD load(data)
23        insertIntoDatabase(data)
24    END METHOD
25END CLASS

The parent defines the skeleton (mine → extract → transform → load). Children fill in the specific steps. This is the Template Method pattern from Lesson 8, and it's everywhere: ASP.NET request pipelines, React component lifecycles, Spring Boot controllers.

Partially completed structure with clear markings for remaining sections
An abstract class is a partially built structure. The walls are up, the roof is on, but the interior is blank. Each child class fills in the rooms differently. The framework is shared. The specifics are individual.

Interfaces: Capability Without Ancestry

An interface declares what an object can do without providing any implementation and without requiring shared ancestry.

1INTERFACE IExportable
2    METHOD exportToPdf(): Bytes
3    METHOD exportToCsv(): String
4END INTERFACE
5
6// Completely unrelated classes can implement the same interface:
7CLASS Invoice IMPLEMENTS IExportable
8    METHOD exportToPdf() // Invoice-specific PDF
9    METHOD exportToCsv() // Invoice-specific CSV
10END CLASS
11
12CLASS EmployeeReport IMPLEMENTS IExportable
13    METHOD exportToPdf() // Report-specific PDF
14    METHOD exportToCsv() // Report-specific CSV
15END CLASS

Invoice and EmployeeReport have no common ancestor. They're not related by inheritance. But they both implement IExportable, so any code that needs "something exportable" can accept either one.

Abstract classes are for shared ancestry: "these things are family and share some behavior." Interfaces are for shared capability: "these things can all do the same trick, regardless of family."

The Decision Matrix

QuestionAbstract ClassInterface
Need shared implementation?YesNo (traditionally)
Need multiple "parents"?No (single inheritance)Yes (implement many)
Related by ancestry?Yes (is-a)No (can-do)
Evolving API?Easier (add methods with defaults)Harder (breaks implementors)
3:1
ratio of interfaces to abstract classes in well-designed frameworks like .NET and Java standard libraries. Interfaces are used roughly three times more often because capability contracts are more common than shared ancestry.

Runtime vs Compile-Time Polymorphism

Two kinds of polymorphism exist, and understanding the difference clarifies everything:

Compile-time polymorphism (static dispatch): The compiler resolves which method to call before the program runs. Method overloading and generics are compile-time. print(42) vs print("hello") is resolved by looking at the argument types during compilation.

Runtime polymorphism (dynamic dispatch): The program resolves which method to call while running. Method overriding via vtable lookup. animal.speak() calls Dog.speak or Cat.speak depending on the actual object type at runtime.

Static vs Dynamic Dispatch

Compile-time (static): Faster (no vtable lookup), but less flexible. The types must be known at compile time. Used for: overloading, generics, operator overloading. Runtime (dynamic): Slightly slower (vtable lookup), but infinitely flexible. The types can be anything that satisfies the contract. Used for: overriding, interface dispatch, plugin architectures. Rule of thumb: Use static dispatch for performance-critical paths. Use dynamic dispatch for flexibility and extensibility. Most of your code should use dynamic dispatch because developer time is more expensive than nanoseconds.

"In programming, as in everything else, to be in error is to be reborn." Alan Perlis, the first ACM Turing Award recipient, captured something essential: every override is a class being reborn with new behavior. Same contract. New life.

Multiple convergent paths meeting at a single illuminated destination point
Runtime polymorphism: many types, one interface, one method call. The runtime follows the vtable pointer and arrives at the right implementation every time. The calling code never needs to know which path was taken.
Lesson 11 Practice 0/6
How was this article?

Share

Link copied to clipboard!

You Might Also Like

Lee Foropoulos

Lee Foropoulos

Business Development Lead at Lookatmedia, fractional executive, and founder of gotHABITS.

🔔

Never Miss a Post

Get notified when new articles are published. No email required.

You will see a banner on the site when a new post is published, plus a browser notification if you allow it.

Browser notifications only. No spam, no email.

0 / 0