The `super` Keyword
Calling Up the Inheritance Chain
LinkedIn Hook
Every junior dev I've paired with has hit the same wall at least once.
They extend a class, write a constructor, and immediately get hit with a ReferenceError: Must call super constructor in derived class before accessing 'this'. The error message is clear. But why it happens — and what JavaScript is actually doing — is less obvious.
superis one of those keywords that looks simple until you try to explain it in an interview. Then you realise there are at least four things it can do, each with different rules.Lesson 4.5 covers all of them. Calling the parent constructor. Calling parent methods. Using
superinside overridden methods. Staticsuper. And the exact moment forgettingsuper()in a subclass constructor goes from a bad idea to a hard crash.Read the full lesson -> [link]
#OOP #JavaScript #Inheritance #InterviewPrep
What You'll Learn
- What
superis and why JavaScript requires it in subclass constructors - How
super()calls the parent constructor and passes arguments up the chain - How
super.method()calls a parent method from inside an overridden method - What happens when you forget
super()in a subclass constructor (ReferenceError, and why) - How
superworks with static methods - The exact rules that govern when and where
supercan be used
The Analogy — Checking In With the Manager Before Using the Office
Picture a new employee joining a company. Before they can set up their desk, use the equipment, or access the systems, they have to check in with the manager who sets up their account. The manager runs a setup process that creates the core workspace. The employee can then add their own customizations on top.
In JavaScript inheritance, the parent class is the manager. The subclass is the new employee. Before the subclass constructor can use this — create properties, call methods, reference anything — it must call super() to let the parent class run its setup first. Only then is the workspace ready.
Skip the check-in and you get a hard error. JavaScript will not let you touch this in a subclass constructor until super() has been called.
What Is super?
super is a keyword with two distinct uses. Inside a constructor, super() calls the parent class constructor. Inside a method, super.methodName() calls the parent class version of that method. These look similar but operate differently.
super is not a variable. You cannot assign it, pass it, or log it. It is a binding that the JavaScript engine resolves based on the class hierarchy at the point where the containing method was defined. This distinction matters when you get into method overriding and static dispatch.
Example 1 — Calling the Parent Constructor with super()
The first and most common use. When a subclass defines a constructor, it must call super() before touching this.
class Vehicle {
constructor(make, model, year) {
// The parent constructor initializes the shared properties
// Every vehicle has these, regardless of type
this.make = make;
this.model = model;
this.year = year;
this.isRunning = false;
}
start() {
this.isRunning = true;
return `${this.make} ${this.model} started.`;
}
stop() {
this.isRunning = false;
return `${this.make} ${this.model} stopped.`;
}
toString() {
return `${this.year} ${this.make} ${this.model}`;
}
}
class ElectricVehicle extends Vehicle {
constructor(make, model, year, batteryCapacityKwh) {
// super() MUST come before any reference to 'this'
// It calls Vehicle's constructor with the shared arguments
// 'this' does not exist until super() returns
super(make, model, year);
// Now 'this' is safe to use — parent setup is done
// We add the EV-specific property on top
this.batteryCapacityKwh = batteryCapacityKwh;
this.chargeLevel = 100; // percent
}
charge(amount) {
this.chargeLevel = Math.min(100, this.chargeLevel + amount);
return `${this.toString()} charged to ${this.chargeLevel}%.`;
}
toString() {
// super.toString() calls the parent version of toString()
// We get the base string and add EV-specific info
return `${super.toString()} [EV, ${this.batteryCapacityKwh}kWh]`;
}
}
const tesla = new ElectricVehicle('Tesla', 'Model 3', 2024, 82);
console.log(tesla.make); // "Tesla"
console.log(tesla.model); // "Model 3"
console.log(tesla.year); // 2024
console.log(tesla.batteryCapacityKwh); // 82
console.log(tesla.chargeLevel); // 100
console.log(tesla.start()); // "Tesla Model 3 started."
console.log(tesla.toString()); // "2024 Tesla Model 3 [EV, 82kWh]"
console.log(tesla.charge(0)); // "2024 Tesla Model 3 [EV, 82kWh] charged to 100%."
Two things to notice. First: super(make, model, year) passes arguments directly to Vehicle's constructor. The parent gets exactly what it needs. Second: super.toString() inside ElectricVehicle.toString() calls Vehicle.toString(), which returns the base string. The subclass builds on it instead of duplicating it.
[PERSONAL EXPERIENCE]: The pattern of calling super.toString() and extending the result is something I use constantly when building class hierarchies for debug tooling. It keeps string representations composable — each layer of the hierarchy adds its own info without rewriting what the parent already computed.
Example 2 — super.method() in Method Overriding
You override a method to change its behavior. You call super.method() when you want the new behavior to include — rather than replace — the parent's behavior.
class Logger {
constructor(name) {
this.name = name;
this.logs = [];
}
log(message) {
// Base logger: store the message and print with timestamp
const entry = `[${new Date().toISOString()}] [${this.name}] ${message}`;
this.logs.push(entry);
console.log(entry);
return entry;
}
getLogs() {
return [...this.logs]; // Return a copy — do not expose the internal array
}
}
class ErrorLogger extends Logger {
constructor(name) {
super(name);
this.errorCount = 0;
}
log(message) {
// Override: increment the error counter, then delegate to the parent
// super.log() handles timestamp formatting and storage — no duplication
this.errorCount++;
const prefix = `[ERROR #${this.errorCount}]`;
return super.log(`${prefix} ${message}`);
// Result: "[timestamp] [name] [ERROR #1] message"
}
summary() {
return `${this.name}: ${this.errorCount} error(s) logged.`;
}
}
class AuditLogger extends ErrorLogger {
constructor(name, auditId) {
super(name);
this.auditId = auditId;
}
log(message) {
// Multi-level override: adds audit ID, delegates to ErrorLogger
// ErrorLogger.log() will then call Logger.log()
// super.log() here refers to ErrorLogger.log(), not Logger.log()
const auditTag = `[AUDIT:${this.auditId}]`;
return super.log(`${auditTag} ${message}`);
// Full result: "[timestamp] [name] [ERROR #1] [AUDIT:XYZ] message"
}
}
const errLog = new ErrorLogger('PaymentService');
errLog.log('Card declined for user #4421');
// [2026-04-22T...] [PaymentService] [ERROR #1] Card declined for user #4421
errLog.log('Timeout connecting to gateway');
// [2026-04-22T...] [PaymentService] [ERROR #2] Timeout connecting to gateway
console.log(errLog.summary()); // "PaymentService: 2 error(s) logged."
const auditLog = new AuditLogger('OrderService', 'ORD-2026-04');
auditLog.log('Stock check failed');
// [2026-04-22T...] [OrderService] [ERROR #1] [AUDIT:ORD-2026-04] Stock check failed
console.log(auditLog.summary()); // "OrderService: 1 error(s) logged."
console.log(auditLog.getLogs().length); // 1
super.log() in AuditLogger resolves to ErrorLogger.log() — not Logger.log(). JavaScript looks up super relative to the class where the method is defined, not the class of the current object. This is called the "home object" binding, and it is fixed at method definition time. It does not change based on how the method is called.
Example 3 — super with Static Methods
super works in static methods too. Inside a static method, super.staticMethod() calls the parent class's static method. The mechanics are the same — one level up the chain — but you are operating on the class itself, not on instances.
class MathUtils {
static round(value, decimals = 0) {
// Base static utility: standard rounding
const factor = Math.pow(10, decimals);
return Math.round(value * factor) / factor;
}
static describe() {
// Static method on the base class — can be overridden
return `MathUtils: general-purpose math helpers`;
}
}
class FinanceMath extends MathUtils {
static round(value, decimals = 2) {
// Override: financial rounding always defaults to 2 decimal places
// Delegate to the parent for the actual rounding logic
return super.round(value, decimals);
// super here refers to MathUtils (the parent class), not an instance
}
static roundUp(value, decimals = 2) {
// Static method unique to FinanceMath — no parent version
const factor = Math.pow(10, decimals);
return Math.ceil(value * factor) / factor;
}
static describe() {
// Call super.describe() to include the parent's description
return `${super.describe()} | FinanceMath: finance-specific rounding`;
}
}
console.log(MathUtils.round(3.14159)); // 3
console.log(MathUtils.round(3.14159, 3)); // 3.142
console.log(FinanceMath.round(3.14159)); // 3.14 (defaults to 2 decimals)
console.log(FinanceMath.round(3.14159, 4)); // 3.1416
console.log(FinanceMath.roundUp(3.141)); // 3.15
console.log(MathUtils.describe());
// "MathUtils: general-purpose math helpers"
console.log(FinanceMath.describe());
// "MathUtils: general-purpose math helpers | FinanceMath: finance-specific rounding"
Inside a static method, super refers to the parent class object — MathUtils — not an instance. Inside an instance method, super refers to the parent class's prototype. These are different lookup targets, but the syntax is identical. This symmetry is intentional: the super keyword behaves consistently in both contexts, just against different objects.
[UNIQUE INSIGHT]: Static super is less commonly tested in interviews, but it reveals whether a candidate understands that JavaScript classes are objects themselves and that extends creates a prototype link between the class objects as well as between their prototypes. FinanceMath.__proto__ === MathUtils is what makes static super work. Most candidates know instance super but can not explain why static super works at all.
Example 4 — The ReferenceError Trap: Forgetting super() in a Subclass Constructor
This is the most common super-related mistake. If you write a subclass constructor and access this before calling super(), JavaScript throws a ReferenceError. Not a TypeError. Not a silent failure. A hard crash with a specific message.
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
return `${this.name} says: ${this.sound}!`;
}
}
// MISTAKE 1: Accessing 'this' before calling super()
class Dog extends Animal {
constructor(name, breed) {
// ERROR: Trying to use 'this' before super() is called
// JavaScript will throw: ReferenceError: Must call super constructor
// in derived class before accessing 'this' or returning from derived constructor
this.breed = breed; // <-- crash happens here
super(name, 'Woof');
}
}
try {
const dog = new Dog('Rex', 'Labrador');
} catch (e) {
console.log(e instanceof ReferenceError); // true
console.log(e.message);
// "Must call super constructor in derived class before accessing
// 'this' or returning from derived constructor"
}
// MISTAKE 2: Omitting super() entirely when a constructor is defined
class Cat extends Animal {
constructor(name, indoor) {
// No super() call at all
// Same ReferenceError — 'this' is not initialized
this.indoor = indoor; // <-- crash happens here
}
}
try {
const cat = new Cat('Whiskers', true);
} catch (e) {
console.log(e instanceof ReferenceError); // true
console.log(e.message);
// Same error message as above
}
// CORRECT: super() first, then 'this'
class CorrectDog extends Animal {
constructor(name, breed) {
super(name, 'Woof'); // Parent runs first — 'this' is now initialized
this.breed = breed; // Safe to use 'this' now
}
describe() {
// super.speak() calls Animal.speak() with the current instance's data
return `${super.speak()} (Breed: ${this.breed})`;
}
}
const rex = new CorrectDog('Rex', 'Labrador');
console.log(rex.name); // "Rex"
console.log(rex.sound); // "Woof" (set by Animal constructor via super)
console.log(rex.breed); // "Labrador"
console.log(rex.speak()); // "Rex says: Woof!"
console.log(rex.describe()); // "Rex says: Woof! (Breed: Labrador)"
Why does JavaScript enforce this order? Because the parent constructor is responsible for setting up the internal slot that backs this. In native classes, the object allocation itself happens inside the base class constructor. Until super() runs and returns, this is in an uninitialized state. The specification calls this "the [[thisBinding]] is not yet initialized". Accessing it before that initialization completes is a spec violation — hence ReferenceError, not TypeError.
[ORIGINAL DATA]: The exact error message differs slightly across engines. V8 (Node.js, Chrome) throws "Must call super constructor in derived class before accessing 'this' or returning from derived constructor". SpiderMonkey (Firefox) says "ReferenceError: |this| used uninitialized in ClassName class constructor". The type is always ReferenceError across all major engines — the message is engine-specific.
Why You Can Omit super() When There Is No Explicit Constructor
A subclass with no constructor block at all works fine. JavaScript inserts a default constructor that looks like this:
constructor(...args) {
super(...args);
}
The engine adds it automatically. You only hit the super() requirement when you write your own constructor. The moment you write constructor() { ... } in a subclass, the automatic version is replaced and you are responsible for calling super() manually.
class Bird extends Animal {
// No constructor defined here
// JavaScript inserts: constructor(...args) { super(...args); }
}
// This works fine — the implicit constructor calls super() for you
const parrot = new Bird('Polly', 'Squawk');
console.log(parrot.speak()); // "Polly says: Squawk!"
Common Mistakes
-
Accessing
thisbeforesuper()in a subclass constructor. The engine will throw aReferenceError.thisdoes not exist untilsuper()returns. The fix is always the same: movesuper()to the first line of the constructor. -
Calling
super()in a base class constructor.super()is only valid in a subclass constructor. A base class that extends nothing cannot callsuper(). If you try, you get aSyntaxErrorduring parsing — not at runtime. -
Calling
super()more than once in a constructor. Each constructor must callsuper()at most once. Calling it a second time throws aReferenceErrorbecausethisis already initialized and the spec does not permit re-initialization. -
Assuming
super.method()always resolves to the immediate parent. It resolves to the class where the containing method was defined, one level up. In a three-level chain,super.log()insideAuditLogger.log()callsErrorLogger.log(), notLogger.log(). The resolution is fixed at method definition time — it does not re-resolve dynamically at call time. -
Forgetting to pass arguments to
super(). If the parent constructor requires arguments,super()with no arguments will leave those parent properties asundefined. The compiler does not warn you. You get silent property corruption that only shows up when you access the properties later. -
Trying to use
superin a plain function or object method.superis only valid inside class methods, class constructors, and object literal methods with a[[HomeObject]]binding. Using it in a standalone function throws aSyntaxError.
Interview Questions
Q: Why does JavaScript throw a ReferenceError when you access this before calling super() in a subclass constructor?
In JavaScript's class system, the object allocation (the creation of the object backing
this) happens inside the base class constructor, not the subclass. Untilsuper()runs and returns,thishas no backing object — the spec calls this the uninitialized[[thisBinding]]state. Any attempt to read or writethisbefore that initialization completes is a spec violation, so the engine throws aReferenceError. The fix is always to movesuper()to the first line of the subclass constructor.
Q: What is the difference between super() and super.method()?
super()is a constructor call. It invokes the parent class'sconstructorfunction and is only valid inside a subclass constructor.super.method()is a method call. It invokes a specific method from the parent class's prototype and is valid inside any class method or accessor. Both use the same keyword but operate in completely different contexts. Usingsuper()outside a constructor orsuper.method()inside a constructor in place of the other will produce aSyntaxErroror incorrect behavior.
Q: How does super work in static methods, and what makes it possible?
In a static method,
superrefers to the parent class object itself (not its prototype). This works becauseextendscreates a prototype link between the class objects:ChildClass.__proto__ === ParentClass. When the engine resolvessuper.staticMethod()inside a static method, it looks up the parent class via that prototype chain. Instancesuperand staticsuperuse the same keyword but point at different objects: instance methods look atParentClass.prototype, static methods look atParentClassdirectly.
Q: A candidate says "I can skip super() if I don't need any parent properties." Is that correct?
No. If a subclass defines an explicit
constructor, it must callsuper()before accessingthis, regardless of whether it needs parent properties. The requirement is not about inheriting data — it is about object initialization. The JavaScript engine requires the parent constructor to run to put the object into a valid state. Skippingsuper()and then touchingthisproduces aReferenceError. The only valid exception is returning a different object from the constructor entirely, which is a very rare and advanced pattern.
Q: If a subclass does not define a constructor, does it need to call super()?
No explicit call is needed because the subclass has no explicit constructor. JavaScript automatically inserts a default constructor equivalent to
constructor(...args) { super(...args); }for any subclass that omits the constructor block. Thesuper()call happens implicitly. The manualsuper()requirement only applies when the developer writes their ownconstructorbody — that replaces the automatic version.
Quick Reference — Cheat Sheet
THE super KEYWORD — COMPLETE REFERENCE
========================================
USE 1: super() — Call Parent Constructor
------------------------------------------
class Child extends Parent {
constructor(a, b, extra) {
super(a, b); // Must be first line before any 'this' access
this.extra = extra; // Safe after super() returns
}
}
Rules:
- Only valid inside a subclass constructor
- Must come before any reference to 'this'
- Must be called exactly once
- Pass required arguments to the parent constructor explicitly
USE 2: super.method() — Call Parent Method
--------------------------------------------
class Child extends Parent {
greet() {
const base = super.greet(); // Calls Parent.prototype.greet()
return `${base} (extended)`;
}
}
Rules:
- Valid inside any instance method or accessor
- Resolves relative to the class where the method is DEFINED (not the caller)
- Does NOT re-resolve dynamically — binding is fixed at definition time
- Can be called at any point inside the method — not restricted to first line
USE 3: super.staticMethod() — Call Parent Static Method
---------------------------------------------------------
class Child extends Parent {
static util() {
return super.util(); // Calls Parent.util() (the static method on the class object)
}
}
Rules:
- Valid inside static methods only
- super refers to the parent CLASS object (not its prototype)
- Enabled by: ChildClass.__proto__ === ParentClass (set by extends)
THE ReferenceError TRAP
------------------------
class Child extends Parent {
constructor(name) {
this.name = name; // ERROR: ReferenceError before super()
super();
}
}
// ReferenceError: Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor
Fix: move super() before all 'this' access.
OMITTING THE CONSTRUCTOR ENTIRELY
-----------------------------------
class Child extends Parent {
// No constructor — JavaScript inserts automatically:
// constructor(...args) { super(...args); }
}
// Safe — parent constructor is called with forwarded arguments
WHEN super IS NOT NEEDED / NOT ALLOWED
----------------------------------------
- Base classes (not extending anything): no super() call
- Plain functions: super is a SyntaxError
- Arrow functions: no own super binding (inherits from enclosing method)
QUICK DECISION TABLE
---------------------
Situation What to call
----------- ------------
Subclass constructor, need parent init super(arg1, arg2, ...)
Override method, want parent behavior super.methodName(args)
Override static, want parent behavior super.staticName(args) (inside static)
No constructor needed in subclass omit constructor entirely
COMMON ERRORS
--------------
Error type Cause
----------- -----
ReferenceError this used before super() in subclass constructor
ReferenceError super() called twice in same constructor
SyntaxError super() called in a base class (not extending anything)
SyntaxError super used in a plain (non-class) function
Silent bug super() called without required arguments -> parent props are undefined
Previous: Lesson 4.4 - Multiple Inheritance and the Diamond Problem Next: Lesson 4.6 - Composition vs Inheritance
This is Lesson 4.5 of the OOP Interview Prep Course — 8 chapters, 41 lessons.