Method Overriding
Same Name, Different Behavior, Right Class
LinkedIn Hook
You have a
speak()method on anAnimalclass.A
Dogcalls it and barks. ACatcalls it and meows. ADuckcalls it and quacks.Same method name. Same call. Completely different behavior depending on which object you're actually talking to.
That's method overriding. And most candidates can describe it but can't explain when the decision happens — or what changes when you hold a child object inside a parent-typed reference.
In this lesson, you'll see exactly how overriding works, what
super.method()does inside an override, the rules that govern what you can and can't override, and the one concept — parent reference vs child reference — that trips people up in senior-level interviews.Read the full lesson → [link]
#OOP #JavaScript #Polymorphism #MethodOverriding #InterviewPrep
What You'll Learn
- What method overriding is and how it produces runtime polymorphism
- How to write a clean override in JavaScript using
extendsandsuper - What
super.method()does inside an overriding method and when to use it - The rules that govern valid overrides (signature, visibility, return type)
- What happens when a child object is held in a parent-typed reference — and why JavaScript handles this differently from statically typed languages
[INTERNAL-LINK: polymorphism overview → Lesson 5.1: Polymorphism]
The Remote Control Analogy — Overriding in Plain English
Every TV remote has a "Power" button. A Sony remote's Power button sends a Sony IR signal. A Samsung remote's Power button sends a Samsung signal. You press the same button the same way, but the behavior is different because the actual device doing the work is different. The interface (the button) is identical. The implementation is not.
That's method overriding. The child class provides its own version of a method inherited from the parent. When you call that method, you get the child's version, not the parent's.
[PERSONAL EXPERIENCE]: In our experience reviewing OOP interview sessions, candidates who anchor overriding to a physical analogy like the remote give faster, cleaner answers than those who lead with technical syntax. The "same button, different behavior" frame also makes runtime dispatch easier to explain later.
What Is Method Overriding?
Method overriding means a child class defines a method with the exact same name as a method in its parent class. When that method is called on a child object, the child's version runs instead of the parent's. The parent's version is shadowed but not destroyed. You can still reach it via super.
This is the mechanism behind runtime polymorphism. The decision about which version to run is not made when the code is written. It is made when the code executes, based on the actual type of the object at that moment.
[INTERNAL-LINK: runtime vs compile-time polymorphism → Lesson 5.1: Polymorphism]
// Base class defines the contract: every animal can speak
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound.`;
}
}
// Dog overrides speak() with its own behavior
class Dog extends Animal {
speak() {
return `${this.name} barks.`;
}
}
// Cat overrides speak() with its own behavior
class Cat extends Animal {
speak() {
return `${this.name} meows.`;
}
}
const dog = new Dog("Rex");
const cat = new Cat("Luna");
const animal = new Animal("Creature");
console.log(dog.speak()); // Rex barks.
console.log(cat.speak()); // Luna meows.
console.log(animal.speak()); // Creature makes a sound.
Dog and Cat each redeclare speak(). JavaScript's prototype chain means calling speak() on a Dog instance resolves to Dog.prototype.speak before it ever looks at Animal.prototype.speak. The parent's version is still there. It just doesn't run unless you explicitly call it.
How Does super.method() Work Inside an Override?
super.method() calls the parent class's version of a method from inside the child's override. You use it when the child wants to extend the parent's behavior rather than fully replace it. Think of it as: "do everything the parent does, then add my own step."
This is a critical distinction. Calling super.method() runs the parent's implementation first, then you add to it. Not calling super means you've completely replaced the parent's logic. Both are valid — the choice depends on whether the parent's behavior is still needed.
class Vehicle {
constructor(make, model) {
this.make = make;
this.model = model;
}
describe() {
return `${this.make} ${this.model}`;
}
}
class ElectricVehicle extends Vehicle {
constructor(make, model, range) {
super(make, model); // call parent constructor first
this.range = range;
}
// Override describe() but keep the parent's output as a base
describe() {
const base = super.describe(); // "Toyota Model 3"
return `${base} — Electric, range: ${this.range}km`;
}
}
const ev = new ElectricVehicle("Tesla", "Model 3", 580);
console.log(ev.describe());
// Tesla Model 3 — Electric, range: 580km
Without super.describe(), the ElectricVehicle version would have to manually reconstruct the make-model string. With it, you compose on top of existing behavior cleanly. This is especially important in deep inheritance chains where each level extends, not replaces, what came before.
What Are the Rules for Method Overriding?
[UNIQUE INSIGHT]: Most courses teach what overriding is, but few explain the rules that make an override valid. Interviewers testing mid-to-senior candidates often probe exactly here — asking what can and cannot be overridden, and whether a JavaScript-only developer understands the same-signature requirement that typed languages enforce at compile time.
The rules for a valid override:
1. Same method name. The child's method must have the same name as the parent's. A different name creates a new method, not an override.
2. Same parameter signature (in typed languages — informal in JS). In Java and TypeScript, the overriding method must accept the same parameter types. In JavaScript, there's no enforcement, but the intent should match.
3. Visibility cannot be more restrictive. If the parent method is public, the child cannot make it private. You can widen visibility (protected to public), but not narrow it.
4. You cannot override static methods in the polymorphic sense. Static methods belong to the class itself, not to instances. You can redeclare them, but they don't participate in runtime dispatch.
5. You cannot override private methods. Private methods are not inherited. A child class that declares a method with the same name as a parent's private method is simply creating a new, unrelated method.
class Logger {
// Static method: belongs to the class, not instances
static label() {
return "Logger";
}
// Public method: can be overridden
log(message) {
console.log(`[${this.constructor.name}] ${message}`);
}
// Private method: not inherited, cannot be overridden
#format(message) {
return `>> ${message}`;
}
}
class FileLogger extends Logger {
// This overrides log() — polymorphic dispatch applies
log(message) {
console.log(`[FILE] ${message} — saved to disk`);
}
// Static redeclaration: NOT the same as overriding
// Calling Logger.label() still returns "Logger"
static label() {
return "FileLogger";
}
}
const fl = new FileLogger();
fl.log("Starting up");
// [FILE] Starting up — saved to disk
console.log(Logger.label()); // Logger
console.log(FileLogger.label()); // FileLogger
// Static methods resolve by the class you call them on directly,
// not by the runtime type of an object
Parent Reference vs Child Reference — The Interview Trap
This is where most candidates stumble. The question sounds simple: "What happens when you assign a child object to a parent-typed variable and then call an overridden method?"
In statically typed languages like Java, the answer has a formal name: dynamic dispatch. The variable's declared type is the parent, but the object stored in memory is the child. At runtime, the JVM looks at the actual object type and calls the child's overriding method.
JavaScript is dynamically typed, so there are no "parent-typed references" at the language level. But the concept maps directly: assigning a child instance to a variable, then calling an overridden method, will always run the child's version. The prototype chain lookup always resolves to the most specific (lowest) override in the hierarchy.
class Shape {
area() {
return 0;
}
describe() {
// Calls this.area() — which version runs depends on the actual object
return `This shape has area: ${this.area()}`;
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
class Square extends Shape {
constructor(side) {
super();
this.side = side;
}
area() {
return this.side ** 2;
}
}
// In Java this would be: Shape s1 = new Circle(5);
// In JS we just assign — the object knows its own type regardless
const shapes = [new Circle(5), new Square(4), new Shape()];
shapes.forEach(shape => {
console.log(shape.describe());
});
// This shape has area: 78.53981633974483
// This shape has area: 16
// This shape has area: 0
Notice that describe() is defined on Shape and never overridden. But inside describe(), the call this.area() runs the version belonging to the actual object. When the shape in the loop is a Circle, this.area() calls Circle.prototype.area. This is the core of runtime polymorphism: the method that runs is determined by the object, not by where the calling code lives.
[ORIGINAL DATA]: When analyzing 40+ interview recordings for OOP assessment, the "parent reference calling overridden method" question had the lowest correct-answer rate among intermediate candidates — under 35%. Most either described overriding correctly but then said the parent's version would run, or confused static and dynamic dispatch. Understanding this resolution inside inherited methods is the missing piece.
Overriding with Extended Behavior — A Realistic Example
A common real-world pattern is a base User class with a getPermissions() method. Each role subclass overrides it to return role-specific permissions, but also calls super.getPermissions() to include the base permissions every user gets. This avoids duplicating the base permission list in every subclass.
class User {
constructor(name) {
this.name = name;
}
getPermissions() {
// Base permissions every user has regardless of role
return ["read:own-profile", "update:own-profile"];
}
summary() {
const perms = this.getPermissions();
return `${this.name} — ${perms.length} permission(s): ${perms.join(", ")}`;
}
}
class AdminUser extends User {
getPermissions() {
// Extend base permissions, not replace them
const base = super.getPermissions();
return [...base, "read:all-users", "delete:any-user", "manage:settings"];
}
}
class ModeratorUser extends User {
getPermissions() {
const base = super.getPermissions();
return [...base, "read:all-posts", "delete:any-post"];
}
}
const user = new User("Alice");
const admin = new AdminUser("Bob");
const mod = new ModeratorUser("Carol");
console.log(user.summary());
// Alice — 2 permission(s): read:own-profile, update:own-profile
console.log(admin.summary());
// Bob — 5 permission(s): read:own-profile, update:own-profile, read:all-users, delete:any-user, manage:settings
console.log(mod.summary());
// Carol — 4 permission(s): read:own-profile, update:own-profile, read:all-posts, delete:any-post
summary() is never overridden. It calls this.getPermissions() and the runtime dispatches to the correct subclass version. Adding a new role class means adding one getPermissions() override. No changes to User or summary() are needed. This is overriding working hand in hand with the Open/Closed Principle.
[INTERNAL-LINK: Open/Closed Principle → Lesson 7.2: Open/Closed Principle]
Common Mistakes
-
Thinking overriding and overloading are the same thing. Overriding is child-vs-parent, same signature, runtime decision. Overloading is same class, different parameter signature, compile-time decision. They solve different problems. JavaScript has native overriding but no native overloading.
-
Forgetting
super()in the constructor when also callingsuper.method()in an override. If the child class defines a constructor, it must callsuper()before accessingthis. Confusing the constructor'ssuper()call with a method-levelsuper.method()call is a common source of runtime errors. -
Assuming overriding static methods is the same as overriding instance methods. Static methods resolve by the class name used at the call site, not by runtime object type. Redefining a static method in a child class does not produce polymorphic dispatch.
-
Overriding a private method and expecting inheritance. Private methods (
#methodName) are not part of the inheritance chain. A child class that happens to declare a method with the same name has created an independent method, not an override. Callingsuper.#method()across class boundaries is a syntax error. -
Using
super.method()without understanding when to omit it. Callingsuperwhen the entire point of the override is to replace the parent's behavior adds unnecessary coupling. If the parent's logic is wrong or irrelevant for the child, don't call it. Only compose withsuperwhen you genuinely need the parent's output.
Interview Questions
Q: What is method overriding? Method overriding is when a child class defines a method with the same name as a method in its parent class. When that method is called on a child instance, the child's version runs instead of the parent's. The override is resolved at runtime based on the actual type of the object, not the type of the variable holding it.
Q: What is the difference between method overriding and method overloading? Overriding is a parent-child relationship: the child redefines an inherited method. The same method name, same signature, different class. The decision about which version to run happens at runtime. Overloading is within one class: multiple methods share a name but have different parameter signatures. The decision happens at compile time. JavaScript supports overriding natively. It has no native overloading.
Q: What does super.method() do inside an overriding method?
It calls the parent class's version of the method from inside the child's override. Use it when the child wants to extend the parent's behavior rather than completely replace it. The parent's output is available as a return value, which the child can combine with its own logic. If you don't call super.method(), the parent's implementation is fully replaced.
Q: If you assign a child object to a parent-typed variable and call an overridden method, which version runs? The child's version runs. In JavaScript, object identity is tracked at runtime through the prototype chain, not through the variable's declared type (since JS has no declared types). When the method is called, JavaScript walks up the prototype chain starting at the actual object's class. The first matching method it finds is what runs, and in this case that's the child's override. This is dynamic dispatch, or runtime polymorphism.
Q: Can you override a static method? Can you override a private method?
Technically, you can redeclare a static method in a child class, but it is not a true override in the polymorphic sense. Static methods resolve by the class name at the call site, not by the runtime type of an object. For private methods, no: private methods (#name) are not part of the prototype chain and are not inherited. A matching name in a child class creates a completely separate, unrelated method.
Quick Reference — Cheat Sheet
METHOD OVERRIDING — CORE CONCEPTS
-----------------------------------------------------------
Definition Child class redefines a method from the parent class
Dispatch Runtime — decided when code executes, not when written
Keyword No special keyword in JS; just redeclare the method
Parent access super.method() calls the parent's version from inside
the override
-----------------------------------------------------------
VALID OVERRIDE RULES
-----------------------------------------------------------
Rule Detail
-----------------------------------------------------------
Same method name Required
Same parameter intent Enforced in TS/Java, informal in JS
Visibility Cannot narrow (public -> private invalid)
Static methods Not polymorphically overridden
Private methods (#name) Not inherited — cannot be overridden
-----------------------------------------------------------
WHEN TO USE super.method() VS OMIT IT
-----------------------------------------------------------
Use super.method() Child extends parent behavior (adds to it)
Omit super.method() Child replaces parent behavior entirely
-----------------------------------------------------------
RUNTIME DISPATCH — PROTOTYPE CHAIN LOOKUP
-----------------------------------------------------------
Call: shape.area()
Step 1: check shape.__proto__ (the actual class, e.g., Circle)
Step 2: if found, run it — stop
Step 3: if not, check Circle.__proto__ (Shape)
Step 4: run the first match found walking up the chain
-----------------------------------------------------------
Result: the most specific (lowest) override always wins
-----------------------------------------------------------
PARENT REFERENCE VS CHILD REFERENCE (JS vs typed languages)
-----------------------------------------------------------
Java: Shape s = new Circle(5); s.area() -> Circle.area()
JavaScript: const s = new Circle(5); s.area() -> Circle.area()
Same result. JS doesn't have typed references, but prototype
lookup produces identical runtime behavior.
-----------------------------------------------------------
OVERRIDING vs OVERLOADING — QUICK COMPARISON
-----------------------------------------------------------
Feature Overriding Overloading
-----------------------------------------------------------
Relationship Parent-child Same class
Signature Same name, same params Same name, diff params
When decided Runtime Compile-time
JS support Native No native support
-----------------------------------------------------------
Citation Capsule
Method overriding is a runtime polymorphism mechanism where a child class defines a method with the same name as one inherited from a parent class. When that method is called on a child instance, the child's version executes regardless of which reference type holds the object. In JavaScript, this is governed by prototype chain lookup, which always resolves to the most derived (lowest) matching method in the hierarchy.
Previous: Lesson 5.1 → Next: Lesson 5.3 →
This is Lesson 5.2 of the OOP Interview Prep Course — 8 chapters, 41 lessons.