Inheritance with extends & super
Learn how JavaScript class inheritance connects prototypes, why derived constructors must call super, how method overrides work, and why super follows a method's original home.
- 01Follow the links extends makesExplain how a child class finds the parent's methods and static fields.
- 02Write a child constructor that worksCall
super()before usingthis, and say in what order fields are set. - 03Replace or build on a parent methodWrite a child method that replaces the parent's, or one that calls it with
super.method().
A child class starts from a parent class
In the Class basics lesson, you wrote classes with a constructor, methods, getters, setters, and fields. This lesson adds one new idea: a class can extend another class.
Say a User class already stores a name and describes a normal account. You want an admin account. An admin is a user with a few extra powers. You don't want to write the user code again.
So you write class Admin extends User. Some words for this:
Useris the parent class.Adminis the child class.- The child can use everything the parent has. This is called inheritance.
- The child only needs code for the parts that are new or different.
A parent has a recipe book with soup, bread, and salad. The child gets a book that starts with all of those recipes. Then the child adds a spicy sauce and rewrites the salad. The child doesn't copy the soup recipe out by hand.
- In real life: The parent recipe book
- In JavaScript:
class Userwith its methods - In real life: The child's book starts with those recipes
- In JavaScript:
class Admin extends User - In real life: The child adds a new recipe
- In JavaScript: A new method, like
ban()orsettings() - In real life: The child rewrites one recipe
- In JavaScript: A new
describe()method with the same name
In the F.prototype & constructors lesson, you did this by hand. You linked prototypes with Object.create, and you ran the parent's setup with User.call(this, name). Now two keywords do that work for you:
extendslinks the child class to the parent class.superlets the child call the parent's constructor or the parent's methods.
extends makes two links
STEP THROUGHThe Prototype chain lesson showed how JavaScript finds a property. If an object doesn't have it, JavaScript follows the object's prototype link to another object and looks there. It keeps going until it finds the property or runs out of links.
Class inheritance uses this same search. extends does not copy the parent's methods into the child. It adds links, so the search can reach the parent.
A class is a value too: it is the constructor function itself. So extends makes two links:
Admin.prototypelinks toUser.prototype. Instances use this link to find methods.Adminlinks toUser. The class itself uses this link to find static fields and methods, the ones that belong to the class, not to each object.
Admin.prototype has no greet(), and Admin has no category. Each one links to its User partner, so new Admin().greet() and Admin.category are found there.extends makes two links. Step through both. The next lesson, Static members, covers the second link in full.
script
class User { static category = "person"; greet() { return "hi"; }}class Admin extends User {}console.log(Object.getPrototypeOf(Admin) === User);console.log(new Admin().greet());console.log(Admin.category);The table below compares the old way, by hand, with the class way. Read each row as: "to do this job, the old way writes this, and a class writes this."
| What you want | By hand, with constructor functions | With a class |
|---|---|---|
| Let instances find the parent's methods | Admin.prototype = Object.create(User.prototype) | class Admin extends User {} |
| Run the parent's setup code | User.call(this, name) inside Admin | super(name) inside Admin |
Keep the constructor property right | Set Admin.prototype.constructor back by hand. | The class does it for you. |
| Let the child class find static members | Link the two constructor functions by hand. | Done for you: Object.getPrototypeOf(Admin) === User |
instanceof asks one question: "Is this class's prototype somewhere in the object's chain of links?" An Admin object links to Admin.prototype, which links to User.prototype. So an admin is also a user.
class User {}
class Admin extends User {}
const admin = new Admin();
console.log(admin instanceof Admin);
console.log(admin instanceof User);
console.log(admin instanceof Object);Line by line:
- Line 4 prints
true, becauseadminwas made byAdmin. - Line 5 prints
true, becauseUser.prototypeis the next link afterAdmin.prototype. - Line 6 prints
true, because every chain like this ends atObject.prototype.
class User {
static kind = "person";
}
class Admin extends User {}
console.log(Admin.kind);
console.log(new Admin().kind);Line 2 puts a static field, kind, on the User class. Line 5 prints person. Admin has no kind, so JavaScript follows the link from Admin to User and finds it there.
Line 6 prints undefined. An admin object never sees kind, because static fields are on the class, not on its objects. The next lesson covers static members in full.
So the child never holds copies of the parent's methods. When a method isn't on the child's prototype, JavaScript follows the link and looks on the parent's prototype.
Call super() first in a child constructor
INTERACTIVEA class that extends another class is called a derived class. "Derived" means "made from another one". A derived class has one strict rule for its constructor:
- The parent's constructor builds the new object.
- The child calls it by writing
super(...). - Until
super(...)has run, the object doesn't exist yet, so the child can't usethis.
You and your parent are building a house. You can't hang pictures before anyone has poured the foundation. First your parent pours it. Then you add your own touches.
- In real life: The parent pours the foundation
- In JavaScript:
super(name)runs the parent's constructor - In real life: You decorate after the foundation exists
- In JavaScript: Write
this.role = "admin"aftersuper() - In real life: You can't hang a picture on empty air
- In JavaScript: Using
thisbeforesuper()throws an error
Step through new Admin("Ada") below. Watch the order. It has five steps:
- The child's constructor starts.
- It calls
super(), and the parent's fields get their values. - The parent's constructor body runs.
- Back in the child, the child's fields get their values.
- The rest of the child's constructor runs.
Guess the first log line from new Admin(...), then step through. Change the name: only the name in the log changes, not the order.
script
const log = [];class User { role = log.push("base field: role"); constructor(name) { log.push("base constructor: " + name); this.name = name; }}class Admin extends User { level = log.push("derived field: level"); constructor(name) { super(name); log.push("derived constructor: after super"); }}new Admin("Ada");console.log(log.join(" → "));const log = [];
class User {
role = log.push("base field");
constructor() { log.push("base constructor"); }
}
class Admin extends User {
level = log.push("child field");
constructor() {
super();
log.push("child after super");
}
}
new Admin();
console.log(log.join(" | "));Each step adds a note to the log array. Line 14 prints the notes in order: base field | base constructor | child field | child after super.
The child's constructor starts first, at line 8. On line 9, super() runs the parent's field (line 3) and the parent's constructor body (line 4). When it returns, the child's field (line 7) runs. Last, line 10 runs.
super(). Only after that does the child's field run, then the rest of the child's constructor.- The child's constructor logs a message before it calls
super(). - The parent's fields get their values.
- The parent's constructor body sets
this.name. - The child's fields get their values.
- The child's constructor runs the line after
super(name). - The child's constructor reads
this.namebeforesuper().
Sort each event: does it happen before the super() call, inside it, or after it returns? One card is a trap: it throws an error if you try it.
If you use this before super(), you get a ReferenceError. In Chrome and Node, which use the V8 engine, the message is: "Must call super constructor in derived class before accessing 'this' or returning from derived constructor". Other browsers word it differently, but the rule is the same.
Replace a parent method, or build on it with super.method()
STEP THROUGHA child class can have a method with the same name as a parent method. This is called overriding. JavaScript looks on the child's prototype first, so it finds the child's method and stops. The parent's version is not used.
Sometimes you don't want to throw the parent's version away. You want to keep it and add one small thing. For that, write super.method() inside the child's method. It runs the parent's version and gives you its result. Then you add your part.
Your parent makes a good plain soup. You ask for a bowl and add chili oil. You don't cook the whole soup again. But if you want a completely different dish, you cook your own from the start.
- In real life: The parent makes the plain soup
- In JavaScript:
super.settings()returns the parent's settings - In real life: The child adds chili oil
- In JavaScript: The child adds
"danger zone" - In real life: The child makes a different dish
- In JavaScript: A child method that doesn't call
super
Step through two kinds of child methods: one replaces the parent's method, and one builds on it with super.method().
script
class User { constructor(name) { this.name = name; } describe() { return this.name + " is a user"; } settings() { return ["profile", "notifications"]; }}class Admin extends User { describe() { return this.name + " is an admin"; } settings() { return [...super.settings(), "danger zone"]; }}console.log(ada.describe());console.log(ada.settings().join(", "));In the replay above, describe() replaces the parent's method and never calls it. settings() calls super.settings() to get the parent's list, then adds one more item. Both are fine. Pick the one that matches what the child needs.
class User {
greet() { return "Hello " + this.name; }
}
class Admin extends User {
greet() { return super.greet() + "!"; }
}
const admin = new Admin();
admin.name = "Ada";
console.log(admin.greet());This prints Hello Ada!. On line 5, super.greet() runs the parent's greet(). Inside it, on line 2, this is still the same admin object. So this.name is "Ada", which line 8 set.
super.greet() finds greet() on User.prototype, but this is still admin. So this.name is "Ada", and the call prints Hello Ada!.Where super starts looking: [[HomeObject]]
STEP THROUGHHow does super know where the parent is? Each method you write with method syntax, like greet() { ... }, gets a hidden note about the object it was written in. The JavaScript specification calls this note [[HomeObject]]. The double square brackets mean it is hidden: your code can't read it.
When the method runs super.greet(), JavaScript goes to the method's home object, follows its prototype link up one step, and looks for greet there.
Your official address is your parents' house. You can stay at a friend's house for a week. Your official mail still goes to your parents' house.
- In real life: The home address on your official papers
- In JavaScript: The method's
[[HomeObject]] - In real life: Staying at a friend's house
- In JavaScript: Copying the method to another object
- In real life: Official mail still goes to your home address
- In JavaScript:
superstill starts from the method's first home
In the code below, other borrows the greet method from parent. When it runs:
thisisother, sothis.nameis"Copied method".- But
superstill goes fromparenttogrand, the method's first home. - It never looks at
otherBase, even thoughotherlinks to it.
This is the surprising rule: super depends on where a method was written, not where you copy it later.
script
const grand = { label: "grand", greet() { return this.name + " via grand"; }};const parent = { __proto__: grand, label: "parent",};const otherBase = { label: "other base", greet() { return this.name + " via other base"; }};const other = { __proto__: otherBase, name: "Copied method" };other.greet = parent.greet;console.log(other.greet());other still has parent as its home. So super.greet() goes to grand, never to otherBase.The result is Copied method via grand → parent. So super only works in methods that have a home. And if you copy a method that uses super to a new object, it won't switch to the new object's parent.
Rules and edge cases
Most inheritance code follows a few rules. Read each row as: "in this place, follow this rule, like in this example." Come back to it until the rules feel natural.
| Where you write it | The rule | Example |
|---|---|---|
| In a child's constructor | Call super(...) before you read or write this. | constructor(name) { super(name); this.role = "admin"; } |
| In a child method with the parent's method name | JavaScript finds the child's method first, so it replaces the parent's. | Admin.prototype.describe is used instead of User.prototype.describe. |
In a method that calls super.method() | It runs the parent's version, and this stays the same object. | return super.settings().concat("danger zone") |
| In an arrow function inside a method | The arrow can use super. It borrows super from the method around it. | method() { return () => super.method(); } |
| In a plain function, or an arrow on its own | Don't use super there. It has no home object to start from. | Write it as a class method or an object method, so JavaScript gives it a [[HomeObject]]. |
Here is the most common mistake, and its fix. In the first program, line 6 uses this before line 7 calls super(name), so it throws an error.
class User { constructor(name) { this.name = name; }}class Admin extends User { constructor(name) { this.name = name; super(name); }}new Admin("Ada");class User { constructor(name) { this.name = name; }}class Admin extends User { constructor(name) { super(name); this.role = "admin"; }}console.log(new Admin("Ada").role);In the fixed program, line 6 calls super(name) first. Then line 7 can safely set this.role, and the program prints admin.
After extends, you can write any code that gives back a class. For example, class A extends mixin(B) works if mixin(B) returns a class. The Mixins lesson uses this later. There is also extends null, for a class whose objects don't link to Object.prototype. You will almost never need it.
Where you'll use this
In front-end code today, inheritance is not the first tool for sharing code. The Delegation vs classical inheritance lesson warned about long family trees of classes. Use extends when the child really is a special kind of the parent, and every parent method still makes sense for it.
- A button class in a component library can extend a small base class that holds the setup code every component shares.
- In tests, a fake service class can extend a real one, replace one method, and keep the rest.
- Some frameworks and browser features ask you to extend a class they give you. A later lesson, after private fields, covers extending built-in classes.
You can also extend classes that come with JavaScript. They are called built-in classes:
- A custom error class that extends
Errorkeeps the normal error parts, likemessage. - A list class that extends
Arraykeeps numbered items andlength.
class CheckoutError extends Error {}
class ScoreList extends Array {}
const error = new CheckoutError("Payment declined");
const scores = new ScoreList(10, 8);
console.log(error.name + ": " + error.message);
console.log(scores.length + ", " + scores[1]);
console.log(error instanceof Error);
console.log(scores instanceof Array);Line by line:
- Line 5 prints
Error: Payment declined. The error keeps itsnameandmessage. - Line 6 prints
2, 8. The list has 2 items, and the item at index 1 is8. - Lines 7 and 8 print
true. Each object is still anErroror anArray.
Built-in classes have a few extra rules when you extend them. The later lesson covers those.
One parent and one child is easy to follow. Five levels of parents make it hard to see which code runs. If the child only needs a helper, pass in an object or call a function instead of using extends.
Common misconceptions
"extends copies the parent's methods."
It doesn't copy anything. It links prototypes. When the child doesn't have a method, JavaScript follows the link and finds it on the parent, just like in the Prototype chain lesson.
"The parent's constructor always runs before the child's constructor."
The child's constructor starts first. It just can't use this until super() has run the parent's constructor.
"super is the same as this."
this is the object the method was called on. super says where to find the parent's method, starting from the method's home, [[HomeObject]].
"If I copy a method, super follows the new object."
The copied-method example shows this is false. super keeps using the method's first home.
"Inheritance is always cleaner than other ways to share code."
Inheritance fits when the child is a kind of the parent, like an admin is a user. When one object only uses another, like a cart uses a price helper, give it that object instead. That is called composition, and it is usually easier to change.
Practice: extend, override, and explain
5 EXERCISESRead the finished code, then type what it prints. In your own editor, try deleting super(name) to see why it is needed.
class User {
constructor(name) { this.name = name; }
label() { return this.name; }
}
class Admin extends User {
constructor(name, level) {
super(name);
this.level = level;
}
label() { return super.label() + " (level " + this.level + ")"; }
}
console.log(new Admin("Ada", 2).label());class User {
constructor(name) { this.name = name; }
label() { return this.name; }
}
class Admin extends User {
constructor(name, level) {
super(name);
this.level = level;
}
label() { return super.label() + " (level " + this.level + ")"; }
}
console.log(new Admin("Ada", 2).label());Admin extends User, so it calls the parent's constructor with super(name). Its own label() calls super.label() to get the name, then adds the level. It prints Ada (level 2).
What is the first item in the log when new Admin("Ada") runs?
const log = [];
class User {
role = log.push("base field: role");
constructor(name) {
log.push("base constructor: " + name);
this.name = name;
}
}
class Admin extends User {
level = log.push("derived field: level");
constructor(name) {
log.push("derived constructor: before super");
super(name);
log.push("derived constructor: after super");
}
}
new Admin("Ada");
console.log(log.join(" → "));The first item is derived constructor: before super. Then super() sets the parent's field, runs the parent's constructor body, and returns. Then the child's field is set, and the child's constructor finishes.
The starter code uses this too early. Fix the order so the program prints the admin's role.
class User {
constructor(name) { this.name = name; }
}
class Admin extends User {
constructor(name) {
this.name = name;
super(name);
}
}
new Admin("Ada");class User {
constructor(name) { this.name = name; }
}
class Admin extends User {
constructor(name) {
super(name);
this.role = "admin";
}
}
console.log(new Admin("Ada").role);The fixed constructor calls super(name) first, so this exists. Then it sets the child-only role field, and the program prints admin.
The child keeps the parent's settings and adds one admin-only setting. What does the last line print?
class User {
settings() { return ["profile", "notifications"]; }
}
class Admin extends User {
settings() { return [...super.settings(), "danger zone"]; }
}
console.log(new Admin().settings().join(", "));class User {
settings() { return ["profile", "notifications"]; }
}
class Admin extends User {
settings() { return [...super.settings(), "danger zone"]; }
}
console.log(new Admin().settings().join(", "));Admin's settings() calls the parent's method with super.settings(). The spread ... puts those items into a new array, and "danger zone" goes at the end.
In one word, which object does super.greet() use after the method is copied to other?
const grand = {
label: "grand",
greet() { return this.name + " via grand"; }
};
const parent = {
__proto__: grand,
label: "parent",
greet() { return super.greet() + " → parent"; }
};
const otherBase = {
label: "other base",
greet() { return this.name + " via other base"; }
};
const other = { __proto__: otherBase, name: "Copied method" };
other.greet = parent.greet;
console.log(other.greet());this is other, so the name comes from other. But the method's [[HomeObject]] is still parent, so super.greet() goes one step up from parent, to grand.
Quiz: check your understanding
7 QUESTIONSSome questions ask about ideas. Others ask what code prints. For code, follow the links and the constructor order before you choose.
Question 1 of 7What does
class Admin extends Userset up?Choose an answer to see the explanation.
Question 2 of 7What does the child's greet method print?
Read the code, then predictclass User { greet() { return "user"; } } class Admin extends User { greet() { return "admin"; } } console.log(new Admin().greet());Choose an answer to see the explanation.
Question 3 of 7Why must a child's constructor call
super()before it usesthis?Choose an answer to see the explanation.
Question 4 of 7What does the child's settings method print?
Read the code, then predictclass User { settings() { return ["profile"]; } } class Admin extends User { settings() { return [...super.settings(), "admin"]; } } console.log(new Admin().settings().join(" + "));Choose an answer to see the explanation.
Question 5 of 7What is
[[HomeObject]]for?Choose an answer to see the explanation.
Question 6 of 7What do the static field checks print?
Read the code, then predictclass User { static kind = "person"; } class Admin extends User {} console.log(Object.getPrototypeOf(Admin) === User); console.log(Admin.kind);Choose an answer to see the explanation.
Question 7 of 7Which
extendsline is valid JavaScript?Choose an answer to see the explanation.
Key takeaways
extendslinks the child class to the parent class. It does not copy methods.- A child's constructor must call
super()before it usesthis. - The parent's fields are set inside
super(). The child's fields are set right after it returns. - A child method can replace a parent method, or build on it with
super.method(). superstarts from the method's home,[[HomeObject]]. A copied method keeps its first home.
Remember the one-liner.extends links a child class to a parent class, and super calls the parent's constructor or methods through that link.
Up next: Static members & static blocks.