completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

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.

By the end, you can
  • 01
    Follow the links extends makesExplain how a child class finds the parent's methods and static fields.
  • 02
    Write a child constructor that worksCall super() before using this, and say in what order fields are set.
  • 03
    Replace 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:

  • User is the parent class.
  • Admin is 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.
Real-life analogyextends is a family recipe book

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 User with 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() or settings()
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:

  • extends links the child class to the parent class.
  • super lets the child call the parent's constructor or the parent's methods.

extends makes two links

STEP THROUGH

The 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.prototype links to User.prototype. Instances use this link to find methods.
  • Admin links to User. The class itself uses this link to find static fields and methods, the ones that belong to the class, not to each object.
Two links made by class Admin extends User. Top: the Admin.prototype cart has no greet and links to the User.prototype cart, which holds greet(). Bottom: the Admin cart has no category and links to the User cart, which holds category.Admin.prototypeprototypelinkmethodsUser.prototypegreet()prototypeAdminlinkstatic fieldscategoryUser
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.
See the two links extends makes
Step 0 of 4Ready
Your turn: follow the blue line

extends makes two links. Step through both. The next lesson, Static members, covers the second link in full.

Running in
  1. script
Next: line 6
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);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

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."

Inheritance by hand and with a class
What you wantBy hand, with constructor functionsWith a class
Let instances find the parent's methodsAdmin.prototype = Object.create(User.prototype)class Admin extends User {}
Run the parent's setup codeUser.call(this, name) inside Adminsuper(name) inside Admin
Keep the constructor property rightSet Admin.prototype.constructor back by hand.The class does it for you.
Let the child class find static membersLink 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.

An Admin is also a UserPop out in the code editor (opens in a new tab)JavaScript
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, because admin was made by Admin.
  • Line 5 prints true, because User.prototype is the next link after Admin.prototype.
  • Line 6 prints true, because every chain like this ends at Object.prototype.
Static fields stay on the classPop out in the code editor (opens in a new tab)JavaScript
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

INTERACTIVE

A 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 use this.
Real-life analogysuper() asks your parent to pour the foundation

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" after super()
In real life: You can't hang a picture on empty air
In JavaScript: Using this before super() throws an error

Step through new Admin("Ada") below. Watch the order. It has five steps:

  1. The child's constructor starts.
  2. It calls super(), and the parent's fields get their values.
  3. The parent's constructor body runs.
  4. Back in the child, the child's fields get their values.
  5. The rest of the child's constructor runs.
Step through new Admin(...)
Step 0 of 6Ready
Your turn: follow the blue line

Guess the first log line from new Admin(...), then step through. Change the name: only the name in the log changes, not the order.

Running in
  1. script
Next: line 12
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(" → "));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Change the name passed to new Admin(...)

Only the name in the log changes. The order stays the same.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
The order, in a small examplePop out in the code editor (opens in a new tab)JavaScript
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.

The order of new Admin(). Inside super(): first base field, then base constructor. After super(): next child field, then child after super.inside super()base fieldbase constructorafter super()child fieldchild after super
The parent's field and constructor body run inside super(). Only after that does the child's field run, then the rest of the child's constructor.
When does it run?
  • 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.name before super().
Try it yourself
0 of 6 correct

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.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
The common bug

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 THROUGH

A 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.

Real-life analogysuper.method() asks the parent to cook their version first

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
Replace a method, then build on one with super.method()
Step 0 of 4Ready
Your turn: follow the blue line

Step through two kinds of child methods: one replaces the parent's method, and one builds on it with super.method().

Running in
  1. script
Next: line 10
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(", "));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

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.

super finds the parent's method, but this stays the samePop out in the code editor (opens in a new tab)JavaScript
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() has two arrows. One goes to the User.prototype cart, which holds greet(): that is where the method is found. The other goes to the admin cart, which holds name: Ada: that object is this.super.greet()greet()prototypefound onUser.prototypename: "Ada"adminthis = admin
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 THROUGH

How 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.

Real-life analogy[[HomeObject]] is a method's home address

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: super still starts from the method's first home

In the code below, other borrows the greet method from parent. When it runs:

  • this is other, so this.name is "Copied method".
  • But super still goes from parent to grand, the method's first home.
  • It never looks at otherBase, even though other links to it.
Copy a method that uses super
Step 0 of 3Ready
Your turn: follow the blue line

This is the surprising rule: super depends on where a method was written, not where you copy it later.

Running in
  1. script
Next: line 8
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());
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Top: the parent cart holds greet(), and super goes from parent to the grand cart. Bottom: the other cart holds a copy of greet() and links to otherBase. An arrow from the copy points up to parent: its home. super does not use otherBase.greet()parentsupergreet()grandhome = parentgreet()othernot usedgreet()otherBase
The copy in 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.

Rules for super and inheritance
Where you write itThe ruleExample
In a child's constructorCall super(...) before you read or write this.constructor(name) { super(name); this.role = "admin"; }
In a child method with the parent's method nameJavaScript 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 methodThe 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 ownDon'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.

Bug: this before superPop out in the code editor (opens in a new tab)JavaScript
class User {  constructor(name) { this.name = name; }}class Admin extends User {  constructor(name) {    this.name = name;    super(name);  }}new Admin("Ada");
Fixed: parent first, child secondPop out in the code editor (opens in a new tab)JavaScript
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.

The part after extends can be an expression

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 Error keeps the normal error parts, like message.
  • A list class that extends Array keeps numbered items and length.
Extending Error and ArrayPop out in the code editor (opens in a new tab)JavaScript
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 its name and message.
  • Line 6 prints 2, 8. The list has 2 items, and the item at index 1 is 8.
  • Lines 7 and 8 print true. Each object is still an Error or an Array.

Built-in classes have a few extra rules when you extend them. The later lesson covers those.

Keep inheritance short and simple

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 EXERCISES
Exercise 1 · Warm-upBuild a child class

Read the finished code, then type what it prints. In your own editor, try deleting super(name) to see why it is needed.

Starter codePop out in the code editor (opens in a new tab)JavaScript
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());

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePredict the first log line

    What is the first item in the log when new Admin("Ada") runs?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    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(" → "));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix the this-before-super bug

      The starter code uses this too early. Fix the order so the program prints the admin's role.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      class User {
        constructor(name) { this.name = name; }
      }
      class Admin extends User {
        constructor(name) {
          this.name = name;
          super(name);
        }
      }
      new Admin("Ada");

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeKeep the parent's list and add to it

        The child keeps the parent's settings and adds one admin-only setting. What does the last line print?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        class User {
          settings() { return ["profile", "notifications"]; }
        }
        class Admin extends User {
          settings() { return [...super.settings(), "danger zone"]; }
        }
        console.log(new Admin().settings().join(", "));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeExplain the copied method

          In one word, which object does super.greet() use after the method is copied to other?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          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());

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Some questions ask about ideas. Others ask what code prints. For code, follow the links and the constructor order before you choose.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does class Admin extends User set up?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the child's greet method print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              class User {
                greet() { return "user"; }
              }
              class Admin extends User {
                greet() { return "admin"; }
              }
              console.log(new Admin().greet());

              Choose an answer to see the explanation.

            3. Question 3 of 7Why must a child's constructor call super() before it uses this?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does the child's settings method print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              class 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.

            5. Question 5 of 7What is [[HomeObject]] for?

              Choose an answer to see the explanation.

            6. Question 6 of 7What do the static field checks print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              class 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.

            7. Question 7 of 7Which extends line is valid JavaScript?

              Choose an answer to see the explanation.

            Key takeaways

            • extends links the child class to the parent class. It does not copy methods.
            • A child's constructor must call super() before it uses this.
            • 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().
            • super starts 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.

            CompleteFrontend Clear concepts. Working examples.