JavaScript Inheritance — What Does This Output?
This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Class Inheritance Method Override and improve your technical interview readiness.
class Animal {
constructor(name) { this.name = name }
speak() { return `${this.name} makes a sound` }
}
class Dog extends Animal {
speak() { return `${this.name} barks` }
}
const d = new Dog("Rex")
console.log(d.speak())
Detailed Explanation
Why This Question Matters
If you've spent any time with JavaScript, you know that "classes" aren't actually classes in the way Java or C# developers think of them. Under the hood, JavaScript uses prototypal inheritance. This is where most people trip up.
The snippet provided is a classic test of whether a developer understands method overriding. It looks simple, but it touches on how the JavaScript engine searches for a method on an object and how the extends keyword actually behaves. If you're prepping for a senior interview, you can't just "guess" that it works; you need to know exactly why the engine picks one method over another.
Understanding the Code
Let's look at the players here:
First, we have the Animal class. It defines a constructor to set a name and a speak method. This is our base class.
Then we have Dog, which extends Animal. By doing this, Dog inherits everything from Animal. However, Dog also defines its own speak() method. This is the "override."
When we call new Dog("Rex"), a few things happen:
1. The Dog constructor is called. Since Dog doesn't have its own constructor defined, it implicitly calls the Animal constructor.
2. The name property is assigned to the instance d.
3. The instance d is created with a prototype chain that looks like this: d $\rightarrow$ Dog.prototype $\rightarrow$ Animal.prototype $\rightarrow$ Object.prototype.
Finding the Correct Answer
The output is "Rex barks".
Why? Because of the way JavaScript resolves method calls. When you call d.speak(), the engine doesn't just jump to the base class. It starts at the very bottom of the prototype chain (the instance itself) and works its way up.
1. Does the object d have a property called speak directly on it? No.
2. Does Dog.prototype have a speak method? Yes.
The engine stops right there. It finds the version of speak() defined in the Dog class and executes it. It never even reaches the speak() method in the Animal class because the search is finished.
If the Dog class didn't have a speak() method, the engine would have kept climbing the chain to Animal.prototype and returned "Rex makes a sound."
Common Mistakes Developers Make
The most common mistake is overthinking the super keyword. Some developers assume that if you don't explicitly call super.speak(), the code might break or behave unpredictably. In reality, if you want to override a method, you just redefine it. You only use super if you want to *extend* the base logic rather than *replace* it.
Another point of confusion is the this context. Beginners often wonder if this.name in the Dog class refers to something different than this.name in the Animal class. It doesn't. Both refer to the same instance (d), which was initialized by the constructor.
Lastly, some people confuse this with composition. They might think that because Dog is an Animal, it should do both things. But in class-based inheritance, a child method completely shadows the parent method of the same name.
Real-World Usage
You'll see this pattern everywhere in production code, especially when building UI components or handling API responses.
Imagine you're building a payment system. You might have a base PaymentProcessor class with a process() method that handles logging and validation. Then, you create StripeProcessor and PayPalProcessor that extend the base class.
The base class handles the "boring" stuff (logging the transaction), but the child classes override the process() method to handle the specific API calls for Stripe or PayPal.
In this scenario, you're using the same principle: the child class provides the specific implementation while inheriting the general workflow.
Key Takeaways
- Prototype Chain: JavaScript looks for methods starting from the instance and moving up the prototype chain.
- Method Overriding: Defining a method in a child class with the same name as one in the parent class "shadows" the parent method.
- extends is Sugar: Remember that class and extends are mostly syntactic sugar over JavaScript's prototypal inheritance.
- Search Order: The engine stops at the first match it finds. If Dog has it, Animal is ignored.
Why this matters
Understanding Class Inheritance Method Override is crucial for passing technical interviews. In real-world applications, this concept often leads to subtle bugs if not handled correctly. For more details, you can always refer to the official MDN Documentation.