JavaScript Classes Interview Questions & Answers
JavaScript classes, introduced in ES6, provide a more familiar syntax for creating objects and implementing inheritance. They are syntactic sugar over JavaScript’s existing prototype-based inheritance.
Table of Contents
Class Basics
1. What are classes in JavaScript and how do you create them?
Classes in JavaScript are templates for creating objects. They provide a cleaner syntax for constructor functions and inheritance.
Basic class syntax:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
// Creating instances
const person1 = new Person("John", 30);
const person2 = new Person("Jane", 25);
console.log(person1.greet()); // "Hello, I'm John"
console.log(person2.greet()); // "Hello, I'm Jane"
Class declaration vs expression:
// Class declaration
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
// Class expression
const Circle = class {
constructor(radius) {
this.radius = radius;
}
};
// Named class expression
const Square = class SquareClass {
constructor(side) {
this.side = side;
}
};
2. What is the difference between classes and constructor functions?
Classes are syntactic sugar over constructor functions:
// Constructor function (ES5)
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
// Class (ES6+)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
Key differences:
| Feature | Constructor Function | Class |
|---|---|---|
| Hoisting | Hoisted | Not hoisted (temporal dead zone) |
| Syntax | Function declaration | Class declaration |
| Methods | Added to prototype | Defined in class body |
| Strict mode | Not enforced | Always in strict mode |
| Cannot be called without new | No | Yes (throws error) |
Example:
// Constructor function - can be called without new (not recommended)
function Person(name) {
this.name = name;
}
const person1 = Person("John"); // Works but creates global properties
// Class - must be called with new
class Person {
constructor(name) {
this.name = name;
}
}
// const person2 = Person("John"); // TypeError: Class constructor cannot be invoked without 'new'
3. How do you check if an object is an instance of a class?
Multiple ways to check instance types:
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
bark() {
return "Woof!";
}
}
const dog = new Dog("Buddy");
// Method 1: instanceof operator
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
// Method 2: constructor property
console.log(dog.constructor === Dog); // true
// Method 3: Object.prototype.isPrototypeOf
console.log(Dog.prototype.isPrototypeOf(dog)); // true
console.log(Animal.prototype.isPrototypeOf(dog)); // true
// Method 4: getPrototypeOf
console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true
Constructors
4. What is a constructor and how does it work?
Constructor is a special method that is called when creating an instance of a class.
Key characteristics:
- Must be named
constructor - Automatically called when using
new - Used to initialize object properties
- Can only have one constructor per class
class Car {
constructor(brand, model, year) {
this.brand = brand;
this.model = model;
this.year = year;
this.isRunning = false;
}
start() {
this.isRunning = true;
return `${this.brand} ${this.model} is starting...`;
}
stop() {
this.isRunning = false;
return `${this.brand} ${this.model} is stopping...`;
}
}
const myCar = new Car("Toyota", "Camry", 2020);
console.log(myCar.brand); // "Toyota"
console.log(myCar.isRunning); // false
console.log(myCar.start()); // "Toyota Camry is starting..."
5. What happens if you don’t define a constructor?
If no constructor is defined, JavaScript provides a default constructor:
class SimpleClass {
// No constructor defined
}
// Equivalent to:
class SimpleClass {
constructor() {
// Empty constructor
}
}
const instance = new SimpleClass(); // Works fine
Default constructor behavior:
- Takes no parameters
- Does nothing
- Calls parent constructor if class extends another class
class Parent {
constructor(name) {
this.name = name;
}
}
class Child extends Parent {
// No constructor defined
}
// Equivalent to:
class Child extends Parent {
constructor(...args) {
super(...args); // Calls parent constructor
}
}
const child = new Child("John");
console.log(child.name); // "John"
6. How do you call the parent constructor in a child class?
Use the super() keyword to call the parent constructor:
class Animal {
constructor(name, species) {
this.name = name;
this.species = species;
}
makeSound() {
return "Some sound";
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name, "Dog"); // Call parent constructor
this.breed = breed;
}
makeSound() {
return "Woof!";
}
fetch() {
return `${this.name} is fetching the ball`;
}
}
const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.name); // "Buddy"
console.log(dog.species); // "Dog"
console.log(dog.breed); // "Golden Retriever"
Important rules:
super()must be called before accessingthis- Must be called in constructor if parent has constructor
- Can pass arguments to parent constructor
class Parent {
constructor(name) {
this.name = name;
}
}
class Child extends Parent {
constructor(name, age) {
// super(name); // Must call before using this
this.age = age; // Error if super() not called first
super(name); // Correct order
}
}
Methods
7. What are the different types of methods in classes?
JavaScript classes support several types of methods:
class Calculator {
// Instance method
add(a, b) {
return a + b;
}
// Instance method with arrow function
subtract = (a, b) => {
return a - b;
}
// Static method
static multiply(a, b) {
return a * b;
}
// Getter method
get result() {
return this.lastResult;
}
// Setter method
set result(value) {
this.lastResult = value;
}
// Private method (ES2022+)
#privateMethod() {
return "This is private";
}
// Public method that uses private method
publicMethod() {
return this.#privateMethod();
}
}
const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
console.log(calc.subtract(10, 4)); // 6
console.log(Calculator.multiply(2, 6)); // 12 (static method)
console.log(calc.publicMethod()); // "This is private"
// console.log(calc.#privateMethod()); // SyntaxError: Private field
8. What are static methods and when would you use them?
Static methods belong to the class itself, not instances. They are called on the class, not objects.
Characteristics:
- Called on class, not instances
- Cannot access instance properties/methods
- Cannot use
thiskeyword - Useful for utility functions
class MathUtils {
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static isEven(num) {
return num % 2 === 0;
}
static factorial(n) {
if (n <= 1) return 1;
return n * MathUtils.factorial(n - 1);
}
}
// Calling static methods
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.multiply(4, 6)); // 24
console.log(MathUtils.isEven(10)); // true
console.log(MathUtils.factorial(5)); // 120
// Cannot call on instances
const math = new MathUtils();
// console.log(math.add(5, 3)); // TypeError: math.add is not a function
Common use cases:
class DateUtils {
static formatDate(date) {
return date.toLocaleDateString();
}
static isToday(date) {
const today = new Date();
return date.toDateString() === today.toDateString();
}
static getDaysBetween(date1, date2) {
const diffTime = Math.abs(date2 - date1);
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
}
class StringUtils {
static capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
static reverse(str) {
return str.split('').reverse().join('');
}
static isPalindrome(str) {
const clean = str.toLowerCase().replace(/[^a-z0-9]/g, '');
return clean === clean.split('').reverse().join('');
}
}
9. What are getters and setters in classes?
Getters and setters allow you to define computed properties and control access to class properties.
class Circle {
constructor(radius) {
this._radius = radius; // Convention: underscore for "private"
}
// Getter for radius
get radius() {
return this._radius;
}
// Setter for radius
set radius(value) {
if (value <= 0) {
throw new Error("Radius must be positive");
}
this._radius = value;
}
// Getter for diameter (computed property)
get diameter() {
return this._radius * 2;
}
// Setter for diameter
set diameter(value) {
this._radius = value / 2;
}
// Getter for area (computed property)
get area() {
return Math.PI * this._radius ** 2;
}
// Getter for circumference (computed property)
get circumference() {
return 2 * Math.PI * this._radius;
}
}
const circle = new Circle(5);
console.log(circle.radius); // 5
console.log(circle.diameter); // 10
console.log(circle.area); // 78.54...
console.log(circle.circumference); // 31.41...
circle.radius = 10; // Uses setter
console.log(circle.diameter); // 20
console.log(circle.area); // 314.15...
// circle.radius = -5; // Error: Radius must be positive
Validation and computed properties:
class BankAccount {
constructor(initialBalance) {
this._balance = initialBalance;
this._transactions = [];
}
get balance() {
return this._balance;
}
set balance(value) {
throw new Error("Cannot directly set balance. Use deposit() or withdraw()");
}
get transactions() {
return [...this._transactions]; // Return copy to prevent external modification
}
get isOverdrawn() {
return this._balance < 0;
}
deposit(amount) {
if (amount <= 0) {
throw new Error("Deposit amount must be positive");
}
this._balance += amount;
this._transactions.push({ type: 'deposit', amount, date: new Date() });
}
withdraw(amount) {
if (amount <= 0) {
throw new Error("Withdrawal amount must be positive");
}
if (amount > this._balance) {
throw new Error("Insufficient funds");
}
this._balance -= amount;
this._transactions.push({ type: 'withdrawal', amount, date: new Date() });
}
}
Inheritance
10. How does inheritance work in JavaScript classes?
Inheritance allows a class to inherit properties and methods from another class using the extends keyword.
class Animal {
constructor(name, species) {
this.name = name;
this.species = species;
}
makeSound() {
return "Some sound";
}
getInfo() {
return `${this.name} is a ${this.species}`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name, "Dog");
this.breed = breed;
}
makeSound() {
return "Woof!";
}
fetch() {
return `${this.name} is fetching the ball`;
}
}
class Cat extends Animal {
constructor(name, color) {
super(name, "Cat");
this.color = color;
}
makeSound() {
return "Meow!";
}
climb() {
return `${this.name} is climbing the tree`;
}
}
const dog = new Dog("Buddy", "Golden Retriever");
const cat = new Cat("Whiskers", "Orange");
console.log(dog.getInfo()); // "Buddy is a Dog"
console.log(dog.makeSound()); // "Woof!"
console.log(dog.fetch()); // "Buddy is fetching the ball"
console.log(cat.getInfo()); // "Whiskers is a Cat"
console.log(cat.makeSound()); // "Meow!"
console.log(cat.climb()); // "Whiskers is climbing the tree"
11. What is method overriding and how does it work?
Method overriding occurs when a child class defines a method with the same name as a parent class method.
class Vehicle {
constructor(make, model) {
this.make = make;
this.model = model;
}
start() {
return `${this.make} ${this.model} is starting`;
}
stop() {
return `${this.make} ${this.model} is stopping`;
}
getInfo() {
return `${this.make} ${this.model}`;
}
}
class Car extends Vehicle {
constructor(make, model, fuelType) {
super(make, model);
this.fuelType = fuelType;
}
// Override the start method
start() {
return `${super.start()} with ${this.fuelType} engine`;
}
// Override the getInfo method
getInfo() {
return `${super.getInfo()} (${this.fuelType})`;
}
// New method specific to Car
honk() {
return "Beep beep!";
}
}
class ElectricCar extends Car {
constructor(make, model, batteryCapacity) {
super(make, model, "Electric");
this.batteryCapacity = batteryCapacity;
}
// Override the start method again
start() {
return `${this.make} ${this.model} is starting silently`;
}
// New method specific to ElectricCar
charge() {
return `${this.make} ${this.model} is charging`;
}
}
const car = new Car("Toyota", "Camry", "Gasoline");
const electricCar = new ElectricCar("Tesla", "Model 3", "75kWh");
console.log(car.start()); // "Toyota Camry is starting with Gasoline engine"
console.log(car.getInfo()); // "Toyota Camry (Gasoline)"
console.log(car.honk()); // "Beep beep!"
console.log(electricCar.start()); // "Tesla Model 3 is starting silently"
console.log(electricCar.getInfo()); // "Tesla Model 3 (Electric)"
console.log(electricCar.charge()); // "Tesla Model 3 is charging"
12. How do you check the inheritance chain?
Multiple ways to check inheritance relationships:
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
}
class GoldenRetriever extends Dog {
constructor(name) {
super(name, "Golden Retriever");
}
}
const dog = new GoldenRetriever("Buddy");
// Method 1: instanceof operator
console.log(dog instanceof GoldenRetriever); // true
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
// Method 2: constructor property
console.log(dog.constructor === GoldenRetriever); // true
console.log(dog.constructor === Dog); // false
// Method 3: isPrototypeOf
console.log(GoldenRetriever.prototype.isPrototypeOf(dog)); // true
console.log(Dog.prototype.isPrototypeOf(dog)); // true
console.log(Animal.prototype.isPrototypeOf(dog)); // true
// Method 4: getPrototypeOf chain
let current = Object.getPrototypeOf(dog);
while (current) {
console.log(current.constructor.name);
current = Object.getPrototypeOf(current);
}
// Output: GoldenRetriever, Dog, Animal, Object
Static Members
13. What are static properties and how do you use them?
Static properties belong to the class itself, not instances.
class Database {
static connectionCount = 0;
static maxConnections = 10;
constructor() {
if (Database.connectionCount >= Database.maxConnections) {
throw new Error("Maximum connections reached");
}
Database.connectionCount++;
this.id = Database.connectionCount;
}
static getConnectionCount() {
return Database.connectionCount;
}
static resetConnections() {
Database.connectionCount = 0;
}
disconnect() {
Database.connectionCount--;
return `Connection ${this.id} disconnected`;
}
}
// Using static properties
console.log(Database.connectionCount); // 0
console.log(Database.maxConnections); // 10
const db1 = new Database();
const db2 = new Database();
console.log(Database.getConnectionCount()); // 2
console.log(db1.disconnect()); // "Connection 1 disconnected"
console.log(Database.getConnectionCount()); // 1
Static properties with inheritance:
class Vehicle {
static vehicleCount = 0;
constructor() {
Vehicle.vehicleCount++;
}
static getCount() {
return Vehicle.vehicleCount;
}
}
class Car extends Vehicle {
static carCount = 0;
constructor() {
super();
Car.carCount++;
}
static getCarCount() {
return Car.carCount;
}
}
const car1 = new Car();
const car2 = new Car();
const vehicle = new Vehicle();
console.log(Vehicle.getCount()); // 3 (total vehicles)
console.log(Car.getCarCount()); // 2 (only cars)
console.log(Car.getCount()); // 3 (inherited static method)
Private Fields
14. What are private fields and how do you use them?
Private fields (ES2022+) allow you to create truly private properties that cannot be accessed outside the class.
class BankAccount {
#balance = 0; // Private field
#accountNumber; // Private field declaration
#transactions = []; // Private field
constructor(accountNumber, initialBalance = 0) {
this.#accountNumber = accountNumber;
this.#balance = initialBalance;
}
// Public method to access private field
getBalance() {
return this.#balance;
}
// Public method to access private field
getAccountNumber() {
return this.#accountNumber;
}
deposit(amount) {
if (amount <= 0) {
throw new Error("Deposit amount must be positive");
}
this.#balance += amount;
this.#transactions.push({
type: 'deposit',
amount,
date: new Date()
});
}
withdraw(amount) {
if (amount <= 0) {
throw new Error("Withdrawal amount must be positive");
}
if (amount > this.#balance) {
throw new Error("Insufficient funds");
}
this.#balance -= amount;
this.#transactions.push({
type: 'withdrawal',
amount,
date: new Date()
});
}
// Private method
#validateAmount(amount) {
return amount > 0 && Number.isFinite(amount);
}
// Public method using private method
transfer(amount, targetAccount) {
if (!this.#validateAmount(amount)) {
throw new Error("Invalid amount");
}
this.withdraw(amount);
targetAccount.deposit(amount);
}
}
const account1 = new BankAccount("12345", 1000);
const account2 = new BankAccount("67890", 500);
console.log(account1.getBalance()); // 1000
// console.log(account1.#balance); // SyntaxError: Private field
account1.transfer(200, account2);
console.log(account1.getBalance()); // 800
console.log(account2.getBalance()); // 700
Private fields with inheritance:
class Animal {
#name;
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
}
class Dog extends Animal {
#breed;
constructor(name, breed) {
super(name);
this.#breed = breed;
}
getBreed() {
return this.#breed;
}
getInfo() {
return `${this.getName()} is a ${this.#breed}`;
}
}
const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.getName()); // "Buddy"
console.log(dog.getBreed()); // "Golden Retriever"
console.log(dog.getInfo()); // "Buddy is a Golden Retriever"
// console.log(dog.#name); // SyntaxError: Private field
// console.log(dog.#breed); // SyntaxError: Private field
Advanced Class Concepts
15. What are abstract classes and how can you simulate them?
JavaScript doesn’t have built-in abstract classes, but you can simulate them:
class AbstractVehicle {
constructor() {
if (this.constructor === AbstractVehicle) {
throw new Error("AbstractVehicle cannot be instantiated directly");
}
if (!this.start) {
throw new Error("Subclasses must implement start() method");
}
if (!this.stop) {
throw new Error("Subclasses must implement stop() method");
}
}
// Abstract method (must be implemented by subclasses)
start() {
throw new Error("start() method must be implemented");
}
stop() {
throw new Error("stop() method must be implemented");
}
// Concrete method (can be used by all subclasses)
getInfo() {
return `${this.constructor.name}`;
}
}
class Car extends AbstractVehicle {
constructor(make, model) {
super();
this.make = make;
this.model = model;
}
start() {
return `${this.make} ${this.model} is starting`;
}
stop() {
return `${this.make} ${this.model} is stopping`;
}
getInfo() {
return `${super.getInfo()}: ${this.make} ${this.model}`;
}
}
class Motorcycle extends AbstractVehicle {
constructor(brand, type) {
super();
this.brand = brand;
this.type = type;
}
start() {
return `${this.brand} ${this.type} is starting`;
}
stop() {
return `${this.brand} ${this.type} is stopping`;
}
}
// const vehicle = new AbstractVehicle(); // Error: AbstractVehicle cannot be instantiated directly
const car = new Car("Toyota", "Camry");
const motorcycle = new Motorcycle("Honda", "Sport");
console.log(car.start()); // "Toyota Camry is starting"
console.log(car.getInfo()); // "Car: Toyota Camry"
console.log(motorcycle.start()); // "Honda Sport is starting"
16. How do you implement the Singleton pattern with classes?
Singleton pattern ensures a class has only one instance and provides global access to it.
class Database {
static #instance = null;
#connectionString;
#isConnected = false;
constructor(connectionString) {
if (Database.#instance) {
return Database.#instance;
}
this.#connectionString = connectionString;
Database.#instance = this;
}
static getInstance(connectionString = null) {
if (!Database.#instance) {
Database.#instance = new Database(connectionString);
}
return Database.#instance;
}
connect() {
if (this.#isConnected) {
return "Already connected";
}
this.#isConnected = true;
return `Connected to ${this.#connectionString}`;
}
disconnect() {
if (!this.#isConnected) {
return "Not connected";
}
this.#isConnected = false;
return "Disconnected";
}
query(sql) {
if (!this.#isConnected) {
throw new Error("Not connected to database");
}
return `Executing: ${sql}`;
}
}
// Usage
const db1 = Database.getInstance("mysql://localhost:3306/mydb");
const db2 = Database.getInstance("postgres://localhost:5432/mydb");
console.log(db1 === db2); // true (same instance)
console.log(db1.connect()); // "Connected to mysql://localhost:3306/mydb"
console.log(db2.connect()); // "Already connected"
console.log(db1.query("SELECT * FROM users")); // "Executing: SELECT * FROM users"
17. How do you implement the Factory pattern with classes?
Factory pattern creates objects without specifying their exact class.
class Vehicle {
constructor(type, make, model) {
this.type = type;
this.make = make;
this.model = model;
}
getInfo() {
return `${this.make} ${this.model} (${this.type})`;
}
}
class VehicleFactory {
static createVehicle(type, make, model) {
switch (type.toLowerCase()) {
case 'car':
return new Car(make, model);
case 'motorcycle':
return new Motorcycle(make, model);
case 'truck':
return new Truck(make, model);
default:
throw new Error(`Unknown vehicle type: ${type}`);
}
}
static createCar(make, model) {
return new Car(make, model);
}
static createMotorcycle(make, model) {
return new Motorcycle(make, model);
}
static createTruck(make, model) {
return new Truck(make, model);
}
}
class Car extends Vehicle {
constructor(make, model) {
super('Car', make, model);
}
drive() {
return `${this.getInfo()} is driving on the road`;
}
}
class Motorcycle extends Vehicle {
constructor(make, model) {
super('Motorcycle', make, model);
}
ride() {
return `${this.getInfo()} is riding on the road`;
}
}
class Truck extends Vehicle {
constructor(make, model) {
super('Truck', make, model);
}
haul() {
return `${this.getInfo()} is hauling cargo`;
}
}
// Usage
const car = VehicleFactory.createVehicle('car', 'Toyota', 'Camry');
const motorcycle = VehicleFactory.createMotorcycle('Honda', 'CBR');
const truck = VehicleFactory.createTruck('Ford', 'F-150');
console.log(car.drive()); // "Toyota Camry (Car) is driving on the road"
console.log(motorcycle.ride()); // "Honda CBR (Motorcycle) is riding on the road"
console.log(truck.haul()); // "Ford F-150 (Truck) is hauling cargo"
18. How do you implement mixins with classes?
Mixins are a way to share functionality between classes without inheritance.
// Mixin functions
const Swimmable = (superclass) => class extends superclass {
swim() {
return `${this.name} is swimming`;
}
};
const Flyable = (superclass) => class extends superclass {
fly() {
return `${this.name} is flying`;
}
};
const Walkable = (superclass) => class extends superclass {
walk() {
return `${this.name} is walking`;
}
};
// Base class
class Animal {
constructor(name) {
this.name = name;
}
makeSound() {
return "Some sound";
}
}
// Classes with mixins
class Duck extends Swimmable(Flyable(Walkable(Animal))) {
constructor(name) {
super(name);
}
makeSound() {
return "Quack!";
}
}
class Fish extends Swimmable(Animal) {
constructor(name) {
super(name);
}
makeSound() {
return "Blub blub!";
}
}
class Bird extends Flyable(Walkable(Animal)) {
constructor(name) {
super(name);
}
makeSound() {
return "Tweet!";
}
}
// Usage
const duck = new Duck("Donald");
const fish = new Fish("Nemo");
const bird = new Bird("Tweety");
console.log(duck.swim()); // "Donald is swimming"
console.log(duck.fly()); // "Donald is flying"
console.log(duck.walk()); // "Donald is walking"
console.log(duck.makeSound()); // "Quack!"
console.log(fish.swim()); // "Nemo is swimming"
// console.log(fish.fly()); // Error: fish.fly is not a function
console.log(bird.fly()); // "Tweety is flying"
console.log(bird.walk()); // "Tweety is walking"
Additional Resources
JavaScript classes provide a clean and familiar syntax for object-oriented programming. Understanding classes is essential for modern JavaScript development.
Interview angle
- “Are JS classes real classes?” - no, they are syntax over prototypal inheritance.
classcreates a constructor function whoseprototypeholds the methods;extendssets up the prototype chain. Knowing this explains why methods are shared and whythisbehaves as it does. - “Why does
thisbreak in an extracted method?” -thisis determined by the call site, not the definition. Passingobj.methodas a callback loses the receiver. Bind it, wrap it in an arrow, or define the method as a class field with an arrow function. - “How do you make a field genuinely private?” -
#name. It is enforced by the language, unlike the_nameconvention. Accessing#namefrom outside the class is a syntax error, not a runtime undefined. - “What is a static block for?” - class-level initialisation that needs statements, running once at class definition. Useful for registration and for computing static state from several fields.
- “What must a subclass constructor do?” - call
super()before touchingthis. Skipping it throws, because the base constructor is what allocatesthis.