cf.completefrontendCode editorOpen lab
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
    Read an extends chainExplain how child classes inherit instance and static behavior.
  • 02
    Write safe derived constructorsCall super() before touching this and predict field order.
  • 03
    Override deliberatelyReplace methods or extend parent behavior with super.method().

The idea: a child class starts from a parent class

The Class basics lesson introduces classes as a compact way to write a constructor, methods, getters, setters, and public fields. This lesson adds the next piece: one class can extend another.

If User already knows how to store a name and describe a regular account, Admin extends User can reuse that foundation and add only the admin-specific parts. That is inheritance: a child type delegates missing behavior to a parent type.

Real-life analogyextends is a family recipe book

Imagine every family has a recipe book. The parent book says how to make soup, bread, and salad. The child book starts with all of those recipes available, then adds a spicy sauce and rewrites the salad. That is the feel of extends: start with parent behavior, add or replace only what changes.

In real life: The parent recipe book
In JavaScript: class User with shared 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 ban() or settings() method
In real life: The child rewrites one recipe
In JavaScript: An overriding describe() method

Where the analogy stops: A real book would copy pages. JavaScript does not copy methods; it links objects so lookup can continue to the parent.

This is the class-syntax version of ideas you already saw in F.prototype & constructors: manual inheritance with Object.create and calling a parent constructor like User.call(this, name). extends does that wiring for you, and super gives you a safe way to call the parent constructor or parent method.

extends creates two linked chains

STEP THROUGH

The Prototype chain lesson taught the lookup rule: if a property is not found on one object, JavaScript follows that object’s prototype link. Class inheritance uses that same delegation model. There is no magic bag of copied methods.

A class is also a value: the constructor function object itself. So extends makes two links:

  • Admin.prototype → User.prototype for instance methods.
  • Admin → User for static fields and methods.
See the two chains made by extends
Step 0 of 4Ready
Your turn: follow the blue line

extends creates two linked chains. Step through both before Static members gets its own lesson next.

Running in
  1. script
Next: line 6
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
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);
CallStoreChangeResultRun = next line. 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.
Manual constructor inheritance vs class inheritance
GoalManual patternClass pattern
Connect instance methodsAdmin.prototype = Object.create(User.prototype)class Admin extends User {}
Run parent setupUser.call(this, name) inside Adminsuper(name) inside Admin
Fix constructor propertyReset Admin.prototype.constructor manuallyHandled by class syntax
Inherit staticsLink the constructor functions manuallyObject.getPrototypeOf(Admin) === User

This is why inheritance is still delegation in JavaScript. The child asks its own prototype first. If the method is not there, lookup keeps walking to the parent prototype.

super() in constructors

INTERACTIVE

A class that extends another class is called a derived class. In a derived constructor, this does not exist for you yet. The parent constructor must create and initialize the instance first, so the child must call super(...) before reading or writing this.

Real-life analogysuper() calls your parent to pour the foundation

If you are building a house with your parent, you do not hang paintings before anyone has poured the foundation. A derived constructor has the same rule: let the parent set up the instance, then decorate it with child-specific fields.

In real life: The parent prepares the foundation
In JavaScript: super(name) runs the parent constructor
In real life: You decorate after the foundation exists
In JavaScript: Write this.role = "admin" after super()
In real life: Decorating empty air fails
In JavaScript: Reading this before super() throws

Where the analogy stops: The analogy gives the reason, but the exact order of public fields is a JavaScript rule. The replay below proves the real order.

Step through new Admin("Ada"). Notice the subtle field order: the derived constructor starts, super() begins, base fields initialize, the base constructor body runs, derived fields initialize, and only then does the rest of the derived constructor continue.

Step through new Admin(...)
Step 0 of 6Ready
Your turn: follow the blue line

Predict the first log from new Admin(...), then step through the constructor chain. Change the name to prove only the value changes, not the order.

Running in
  1. script
Next: line 12
Click the blue line to take the next stepPop 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) {    super(name);    log.push("derived constructor: after super");  }}new Admin("Ada");console.log(log.join(" → "));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Change the name passed to new Admin(...)

The value changes, but the construction order does not.

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.
Runs first?
  • A derived constructor can log a message before calling super().
  • Base public fields initialize.
  • The parent constructor body assigns this.name.
  • Derived public fields initialize.
  • The derived constructor runs the line after super(name).
  • Reading this.name in a derived constructor before super().
Try it yourself
0 of 6 correct

Sort each event by whether it happens before the super() call, inside that call, or after it returns. The tricky card throws if you actually try it.

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

In V8-based engines, touching this before super() throws a ReferenceError whose message is: “Must call super constructor in derived class before accessing 'this' or returning from derived constructor”. Other engines may phrase the same rule differently.

Overriding methods, with and without super.method()

STEP THROUGH

An override is a child method with the same name as a parent method. Lookup finds the child method first, so the child can replace behavior. Sometimes that is exactly what you want.

Other times, the child should keep the parent behavior and add one small piece. That is when super.method() is useful: ask the parent to do its version, then add yours.

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

If your parent already makes a good base soup, you can ask for that soup and add chili oil. You do not rewrite the whole recipe. But if the child dish is truly different, you can replace the recipe completely.

In real life: Parent makes the basic soup
In JavaScript: super.settings() returns shared settings
In real life: Child adds chili oil
In JavaScript: Child appends danger zone
In real life: Child makes a totally different dish
In JavaScript: Override without calling super

Where the analogy stops: The parent is not a separate person at runtime. super.method() is a special lookup rule that still calls the method with the current this.

Override, then extend with super.method()
Step 0 of 4Ready
Your turn: follow the blue line

Step through two kinds of overriding: replacing a method completely, and extending the parent method with super.method().

Running in
  1. script
Next: line 10
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
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(", "));
CallStoreChangeResultRun = next line. 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, describe() ignores the parent method, while settings() uses super.settings() to keep the parent settings and add one more. Both are legitimate; choose the one that matches your intent.

super and [[HomeObject]]

STEP THROUGH

super looks simple, but it relies on a hidden spec detail. Methods written with method syntax receive an internal home address called [[HomeObject]]. When the method says super.greet(), JavaScript starts lookup above that home.

Real-life analogy[[HomeObject]] is the method’s fixed home address

Suppose your official address is your parents’ house. You can visit a friend, but official mail still routes through the address on file. A method copied to another object is similar: the call receiver can change, but super still uses the method’s original home.

In real life: A home address on official paperwork
In JavaScript: The method’s [[HomeObject]]
In real life: Visiting a friend’s house
In JavaScript: Copying the method to another object
In real life: Mail still goes to the official address
In JavaScript: super still starts above the original home

Where the analogy stops: The method can still be called with a different this. The fixed part is where super starts lookup, not every value used by the method.

Copy a method that uses super
Step 0 of 3Ready
Your turn: follow the blue line

This is the surprising [[HomeObject]] rule: super is based on where a method was created, not where you later copy it.

Running in
  1. script
Next: line 8
Click the blue line to take the next stepPop 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",};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());
CallStoreChangeResultRun = next line. 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.

That surprising result is why super only works in method contexts that JavaScript can give a home. It is also why you should not casually copy methods that use super and expect them to adapt to a new prototype chain.

Rules and edge cases

Most inheritance code follows a small set of rules. Keep this table nearby until the patterns feel automatic.

Rules for super and inheritance
WhereRuleExample
Derived constructorCall super(...) before reading or writing this.constructor(name) { super(name); this.role = "admin"; }
Method overrideA child method with the same name is found before the parent method.Admin.prototype.describe wins over User.prototype.describe.
super.method()Inside method syntax, call the parent version with the current this.return super.settings().concat("danger zone")
Nested arrowAn arrow inside a method can use that method's super binding.method() { return () => super.method(); }
Standalone arrow/functionDo not define overrides with super in a plain function or detached arrow.Use class or object method syntax so JavaScript creates [[HomeObject]].
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);
extends can use expressions

The right side of extends is an expression. A pattern like class A extends mixin(B) is valid when mixin(B) evaluates to a class. The Mixins lesson explores that idea later. JavaScript also has extends null, a rare edge case for classes with no ordinary object prototype.

Where you’ll use this

In modern front-end code, inheritance is not the default tool for every reuse problem. Delegation vs classical inheritance already warned you about deep family trees. Still, extends is useful when the relationship is honest: the child really is a specialized version of the parent and should honor the same public methods.

  • A design-system component class might extend a small base widget class that owns shared lifecycle hooks.
  • A test helper can extend a fake service and override one method while reusing the rest.
  • Framework and platform APIs often ask you to extend a base class. Extending built-ins gets its own lesson after private fields.
Prefer shallow, boring inheritance

One parent and one child can be clear. Five levels of inheritance can hide cause and effect. If the child only needs a helper, pass an object in or call a function instead of using extends.

Common misconceptions

“extends copies the parent’s methods.”

It links prototypes. Missing properties are found by delegation, just like in the Prototype chain lesson.

“The parent constructor always runs before the child constructor.”

The child constructor is entered first, but it cannot use this until super() runs the parent construction.

“super is the same as this.”

this is the current receiver. super is a parent lookup based on the method’s [[HomeObject]].

“If I copy a method, super follows the new object.”

The copied-method experiment disproves that. super keeps using the method’s original home.

“Inheritance is always cleaner than composition.”

Inheritance is best for true is-a relationships. For “uses a” relationships, composition is usually easier to change.

Practice: inherit, override, and explain

5 EXERCISES
Exercise 1 · Warm-upBuild a subclass

Read the finished version, then type the printed output. 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 constructor log order

    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 a this-before-super bug

      The starter code touches this too early. Fix the order so the program prints the admin 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 · PracticeOverride and extend a method

        Keep the parent settings and add one admin-only setting. What does the final join 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 phrase, which base 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

            These questions mix concepts with real output. Trace the lookup chain and constructor order before choosing.

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

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the overriding 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 derived constructor call super() before using this?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does the super.settings override 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]] used for?

              Choose an answer to see the explanation.

            6. Question 6 of 7What do the static chain 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 form is valid JavaScript?

              Choose an answer to see the explanation.

            Key takeaways

            • extends links child and parent classes; it does not copy methods.
            • A derived constructor must call super() before using this.
            • Base fields initialize inside super(); derived fields initialize after it returns.
            • An override can replace a parent method or build on it with super.method().
            • super in methods uses [[HomeObject]], so copied methods keep their original super lookup.

            Remember the one-liner.
            Class inheritance links a child class to a parent; super calls the parent constructor or method from that linked relationship.

            Up next: Static members & static blocks.

            CompleteFrontend Clear concepts. Working examples.