skip to content

Class Syntax

ES2015+ class syntax and what it compiles down to: prototypes, property descriptors and a stricter constructor protocol. Interviewers probe the seams — super, statics, fields and privacy — precisely where the sugar stops behaving like a class in Java or C#.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

20

In a JavaScript class, what is the difference between `super(...)` and `super.someMethod()`, and where is each one allowed to appear?

level: juniorimportance: must knowfreq 72%

answer

  1. one constructs, one looks up
  2. only one place accepts the call form
  3. the receiver stays the current instance
  4. this.method() inside an override recurses
  5. statics have their own parent link

basics

~20 s

super(...) invokes the parent constructor and is legal only inside the constructor of a class written with extends. super.someMethod() calls the parent's version of a method with the current instance as this, and is legal in any method or accessor.

solid answer

~40 s

They are two different syntactic forms that happen to share a keyword. `super(...)` is the **super call**: it runs the parent constructor and initialises `this`. It is legal in exactly one place — the constructor of a class declared with `extends` — and writing it in a base class constructor or an ordinary function is a `SyntaxError`. `super.someMethod()` is a **super property access**: it looks the name up starting from the parent, then calls it with the current `this` still bound, which is how an override wraps the behaviour it is replacing. That form is legal in any concise method, getter, setter or static method, including in a class with no parent at all, where it simply reaches `Object.prototype`. You cannot use either form in a function expression assigned to a property.

code

javascript · 14 lines
javascript
class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
  constructor(name) {
    super(name);      // the super call: runs Animal's constructor
    this.legs = 4;
  }
  speak() {
    return super.speak() + ' (a bark)'; // the super property
  }
}
console.log(new Dog('Rex').speak()); // Rex makes a sound (a bark)

go deeper

for a junior

Be able to write both forms correctly: super(args) first in a subclass constructor, and super.method() inside an override to reuse the parent's behaviour without recursing.

for a middle

Explain the legality rules — the call form only in a derived constructor, the property form in any method definition including statics and parentless classes — and why the receiver stays the current instance.

for a senior

Bring the failure modes: infinite recursion from this.method() in an override, SyntaxError in function expressions, and why super.x before super() in a constructor still throws.

for a principal

Discuss when a base class should expect subclasses to chain through super at all, since a required super call in an overridable method is an unenforceable contract that makes the base class fragile.

## One keyword, two forms The grammar treats `super` as two separate productions, and the rules differ for each. ### The super call: `super(...)` This one constructs. It finds the parent constructor — the prototype of the constructor currently running — invokes it, and binds the resulting object as `this` in the derived constructor. It is a statement about *object creation*, so it is legal in exactly one position: inside the constructor of a class declared with `extends`. ```js class Animal { constructor(name) { this.name = name; } } class Dog extends Animal { constructor(name) { super(name); // runs Animal's constructor this.legs = 4; } } ``` Everywhere else it is a `SyntaxError`, caught before the code runs: - in the constructor of a class with no `extends` clause; - in a normal method, even of a derived class; - in a standalone function. If a derived class declares no constructor, the engine supplies `constructor(...args) { super(...args); }`, so the call still happens — you just did not write it. ### The super property: `super.x` This one looks up. `super.speak()` starts the search at the parent of the object the method was defined in, finds `speak` there, and calls it with the current `this`. That last part is the whole point: the parent's code runs against the current instance, so it sees the subclass's own state. ```js class Animal { speak() { return `${this.name} makes a sound`; } } class Dog extends Animal { speak() { return super.speak() + ' (a bark)'; } } new Dog(); // this.name inside Animal#speak is the Dog's name ``` Without it, calling `this.speak()` inside the override would call the override again and recurse until the stack overflows — a classic first-week bug. The property form is legal much more widely than the call form: - instance methods, getters and setters; - static methods, where it reaches the parent **class**'s statics rather than the parent's prototype; - concise methods of ordinary object literals, not only classes; - a class with no `extends` at all, where the parent is `Object.prototype`, so `super.toString()` works. It is also **not** limited to reading. `super.x = 1` performs a set that starts its lookup on the parent — relevant when the parent defines a setter — though the property lands on `this`, not on the parent. ### Where neither form works Both forms need the surrounding function to be a *method definition*. A function expression assigned to a property is not one: ```js const obj = { greet: function () { return super.greet(); } }; // SyntaxError ``` An arrow function is a special case for the property form: it has no `super` of its own but inherits the enclosing method's, so `super.speak()` inside a callback written as an arrow inside a method is fine. ## The ordering interaction The two forms meet in one place. Because `super.x()` calls the parent method with `this` as receiver, and `this` does not exist before the super call, a derived constructor cannot use the property form before the call form: ```js class Dog extends Animal { constructor(name) { super.speak(); // ReferenceError: this is not initialised yet super(name); } } ``` So the ordering rule you learn for `super(...)` also constrains `super.x` inside constructors. ## How to answer this crisply Say: "`super(...)` runs the parent constructor and only exists in a derived constructor; `super.method()` calls the parent's version of a method with the current `this` and works in any method, including static ones and even in a class with no parent." Then give the recursion example — `this.speak()` versus `super.speak()` inside an override — because it shows you know *why* the property form exists rather than just where it is legal.

  • What happens if an overriding method calls this.someMethod() instead of super.someMethod()?
    It calls itself. Property lookup on `this` finds the subclass's own override first, so the method re-enters immediately and blows the stack with a `RangeError`. `super.someMethod()` exists precisely to start the lookup one level above the defining class, so the parent's implementation runs against the same instance.
  • Is super.toString() legal in a class that has no extends clause?
    Yes. The property form only needs a parent to look up on, and for a class without `extends` the instance methods' home object is the class's `.prototype`, whose prototype is `Object.prototype`. So `super.toString()` reaches the built-in implementation. Only the super *call* form requires an `extends` clause; writing `super()` in a base constructor is a `SyntaxError`.
  • What does super refer to inside a static method?
    The parent class object, not its prototype. `extends` sets the subclass constructor's own prototype to the parent constructor, so a static method's `super.create()` finds the parent's static `create` and calls it with `this` still bound to whichever class the call went through — which is what makes inherited static factory methods produce the right subclass.

saying these in an interview costs you the question

  • Says super() can be called from any method
  • Thinks this.method() inside an override calls the parent
  • Believes super.method() runs with the parent as this
  • Claims super is unavailable in a class without extends
  • Assumes super works inside any function in a class body

context

open as a page

In a JavaScript class body, what does declaring a field as #count = 0 give you that the older convention of naming it _count does not?

level: juniorimportance: must knowfreq 55%

basics

~20 s

A #-prefixed field is privacy enforced by the engine: it can only be named inside the class body that declares it, it is invisible to Object.keys and JSON.stringify, and touching it elsewhere is an error. An underscore name is only a hint.

open as a page

In a JavaScript class, what does the `static` keyword do — which object does the member live on, and can you reach it from an instance?

level: juniorimportance: must knowfreq 72%

basics

~20 s

static installs a method or field on the class constructor itself instead of on instances. You call it as ClassName.member; reading it from an instance gives undefined, because the constructor is not on the instance's prototype chain.

open as a page

In a JavaScript class that uses `extends`, why must the derived constructor call `super()` before it reads or writes `this`, and what happens if it doesn't?

level: middleimportance: must knowfreq 80%

basics

~20 s

A derived constructor does not create its own this: the parent constructor allocates the instance, and super() is what runs it. Until super() returns, this is uninitialized, so reading or writing it throws a ReferenceError.

open as a page

In a JavaScript class body, where do a field declaration like count = 0 and a method declaration like inc() {} each end up at runtime, and what practical differences follow?

level: middleimportance: must knowfreq 60%

basics

~20 s

A class field becomes an own, writable, enumerable, configurable property created on every instance during construction. A method goes once onto the prototype and is shared. So fields appear in Object.keys, spread and JSON output; methods do not.

open as a page

In JavaScript, when you write `class Point { constructor(x) {} move(dx) {} }`, where does `move` actually live, and how does that differ from writing `Point.prototype.move = function (dx) {}` in the older constructor-function style?

level: middleimportance: must knowfreq 72%

basics

~20 s

Both forms put move on Point.prototype, so lookup is identical. The class defines it as non-enumerable and non-constructible, so for...in and Object.assign skip it, while the hand-written assignment creates an enumerable, constructible property they both pick up.

open as a page

In JavaScript, what happens when you invoke a class constructor without `new` — for example `Point(1, 2)` where `Point` was declared with the `class` keyword — and how did the old constructor-function style behave in the same situation?

level: juniorimportance: should knowfreq 58%

basics

~20 s

It throws a TypeError immediately: class constructors cannot be invoked without new, and the throw happens before any constructor body runs. An old-style constructor function instead ran normally, with this bound to undefined or the global object.

open as a page

When you write `class Child extends Parent {}` in JavaScript, which two prototype links does `extends` set up, and what does each one make possible?

level: middleimportance: should knowfreq 62%

basics

~20 s

extends wires two links: Child.prototype inherits from Parent.prototype, which gives instances the parent's methods, and Child itself inherits from Parent, which gives the class the parent's static members and makes super work in static methods.

open as a page

In a JavaScript class body, declaring onClick = () => {} creates something different from declaring onClick() {}. Where does each one live, and what does that change for memory, overriding, and super calls?

level: middleimportance: should knowfreq 50%

basics

~20 s

An arrow-function class field creates a separate function object on every instance, stored as an own property. A method is created once on the prototype and shared. The field version stays bound when detached, but costs memory per instance and cannot be overridden or reached through super.

open as a page

Given `class Base { static create() { return new this(); } }` and `class Sub extends Base {}`, what does `Sub.create()` return, and why does `Sub` have a `create` method at all?

level: middleimportance: should knowfreq 50%

basics

~20 s

Sub.create() returns a Sub instance. Sub inherits create because extends makes Base the prototype of the Sub constructor object, so static lookups fall through, and this inside a static is the class it was called on — here Sub.

open as a page

What is a `static { ... }` initialization block in a JavaScript class body — when does it run, and what is `this` inside it?

level: middleimportance: should knowfreq 34%

basics

~20 s

A static initialization block is a chunk of statements that runs once while the class is being defined, in source order with the static field initializers. Inside it, this is the class constructor, and it can read the class's private names.

open as a page

In JavaScript, `new Widget()` written above `function Widget() {}` works, but the same call above `class Widget {}` throws. Explain what a class declaration does differently.

level: middleimportance: should knowfreq 48%

basics

~20 s

A class declaration is hoisted but stays uninitialized until evaluated, so touching it earlier throws ReferenceError: Cannot access before initialization. A function declaration is hoisted and initialized with the function, so it is callable from anywhere in its scope.

open as a page

In JavaScript, when a method body contains `super.greet()`, how does the engine decide which object `greet` is looked up on, and why does copying that method onto a different object not change the answer?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Methods written with the shorthand syntax carry a hidden link to the object they were defined in, and super looks the name up on that object's prototype — not on the receiver. Copying the method elsewhere keeps the original link.

open as a page

A JavaScript base class constructor calls a method that the subclass overrides, and that override reads a property the subclass declared as a class field. What happens, and why?

level: seniorimportance: should knowfreq 35%

basics

~20 s

The override runs before the subclass's fields exist, so the property reads as undefined and the code usually throws a TypeError. Subclass field initializers run only after super() returns, while the base constructor body has already finished by then.

open as a page

In JavaScript, a class body is strict-mode code even inside a plain non-module script that never wrote 'use strict'. What concretely changes for code inside the class, and what tends to break when an old constructor function is rewritten as a class?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Every part of a class definition is strict code with no opt-out, so undeclared assignments throw ReferenceError, previously silent write failures become TypeErrors, and legacy sloppy idioms are rejected outright — even when the surrounding script never opted in.

open as a page

You are designing a JavaScript library class whose internal state must not be touched by consumers. Compare private #fields, a module-scoped WeakMap keyed by the instance, and closure-captured variables, and say how you would choose.

level: principalimportance: should knowfreq 28%

basics

~20 s

Default to #fields: cheapest, clearest, enforced by the engine. Switch to a module-scoped WeakMap when several classes in one module must share access or instances get wrapped in a Proxy. Use closures when the API is a factory, not a class hierarchy.

open as a page

In a JavaScript class that declares a private field #id, why does reading other.#id throw when other is not an instance, and what is the recommended way to test whether an arbitrary object carries that field?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Private access performs a brand check first: if the object has no slot for that private name it throws a TypeError instead of returning undefined. Test membership with the ES2022 syntax #id in other, which yields a boolean without throwing.

open as a page

In JavaScript, what does the identifier `Inner` refer to in `const Outer = class Inner { who() { return Inner.name; } };`, and where is it visible?

level: middleimportance: nice to knowfreq 28%

basics

~10 s

Inner is a binding visible only inside the class body, not in the surrounding scope, where the class is reachable only as Outer. That inner binding is immutable, and Outer.name is the string 'Inner'.

open as a page

Why does `class MyArray extends Array {}` produce something that behaves like a real array — a live `length`, index-based behaviour — when the older `function MyArray() { Array.call(this); }` pattern never did?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

Arrays are exotic objects only their own constructor can create. With extends, super() runs Array as a constructor and it allocates the exotic object, using new.target to give it the subclass prototype. Array.call(this) merely calls Array as a function and discards this.

open as a page

A class has `static #count = 0` and `static bump() { this.#count++; }`. Calling `Base.bump()` works, but `Sub.bump()` on `class Sub extends Base {}` throws a TypeError. Why, and how do you fix it?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

A private static member is installed only on the class that declares it. Inside the inherited method this is Sub, which carries no #count slot, so the private access fails its brand check and throws a TypeError. Reference the declaring class by name instead.

open as a page