Abstract Classes
An abstract class is a partly built blueprint. It holds everything a family of classes has in common and leaves blanks each child must fill in. You can never build the blueprint itself.
On this page · 6 min · Beginner
On this page
- In simple words
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Beginner
- Updated
- Oct 28, 2026
Related tutorials
In simple words#
An abstract class is a half-built flat: walls and wiring are done (shared code), but each owner must finish the interiors (abstract methods). Nobody can live in the half-built version.
- abstract class
- Cannot be instantiated.
- Shared code
- Common methods written once.
- Abstract member
- Must be implemented by children.
Learning objectives#
- Explain what makes a class abstract
- Share fields and methods in an abstract base
- Force subclasses to implement abstract members
- Recognise when an abstract class fits
Explanation#
An abstract class cannot be instantiated: there is no such thing as “just an Employee” — only salaried, hourly or contract employees.
It can contain constructors, fields, normal methods and abstract methods. That mix is what makes it useful: shared code plus required blanks.
Subclasses inherit the shared parts and must implement every abstract method. In C#, forgetting one is a compile error.
An abstract class fits when the classes are truly related (“is-a”), share state or code, and you want to control the overall workflow.
JavaScript has no abstract keyword; use the new.target check and throwing methods shown below. TypeScript and C# support abstract directly.
Syntax#
public abstract class Employee(string name)
{
public string Name => name;
public abstract decimal CalculatePay(); // each type decides
public string Payslip() => quot;{Name}: {CalculatePay()}"; // shared
}
public class HourlyEmployee(string name, decimal rate, int hours) : Employee(name)
{
public override decimal CalculatePay() => rate * hours;
}
Code example: One payroll, three kinds of staff#
class Employee {
constructor(name) {
if (new.target === Employee) throw new TypeError('Cannot create an abstract Employee');
this.name = name;
}
calculatePay() { // abstract
throw new Error('calculatePay() not implemented');
}
payslip() { // shared
return `${this.name}: ₹${this.calculatePay()}`;
}
}
class SalariedEmployee extends Employee {
constructor(name, monthly) { super(name); this.monthly = monthly; }
calculatePay() { return this.monthly; }
}
class HourlyEmployee extends Employee {
constructor(name, rate, hours) { super(name); this.rate = rate; this.hours = hours; }
calculatePay() { return this.rate * this.hours; }
}
class Contractor extends Employee {
constructor(name, invoices) { super(name); this.invoices = invoices; }
calculatePay() { return this.invoices.reduce((sum, a) => sum + a, 0); }
}
const staff = [
new SalariedEmployee('Asha', 60000),
new HourlyEmployee('Ravi', 500, 80),
new Contractor('Meera', [15000, 22000]),
];
staff.forEach(e => console.log(e.payslip()));
const total = staff.reduce((sum, e) => sum + e.calculatePay(), 0);
console.log('Payroll total:', total);
try { new Employee('Ghost'); } catch (e) { console.log(e.message); }
Asha: ₹60000 Ravi: ₹40000 Meera: ₹37000 Payroll total: 137000 Cannot create an abstract Employee
Step-by-step explanation#
- Employee holds what every employee has: a name and the payslip format.
- calculatePay is abstract: each subclass calculates pay its own way.
- payslip() is written once and works for all three types because it calls this.calculatePay().
- The payroll code only deals with “an Employee” — it never checks which kind.
- Creating a plain Employee is blocked.
Real-world use case#
A document-export feature has an abstract Exporter with Export() that loads the data, calls the abstract Render() and saves the file. PdfExporter and CsvExporter only write Render().
Common mistakes#
If there is no shared code or state, use an interface — it is lighter and a class can implement several.
A Car and an Invoice both being “Printable” is a capability, not an is-a relationship. That is an interface.
A base with 30 members forces every child to carry them all. Keep the base small.
Best practices#
- Put shared state and workflow in the abstract class
- Keep abstract members few and clearly named
- Use abstract classes for closely related types only
- Combine with interfaces for capabilities
Remember this#
Abstract class = shared code + required blanks.
Cannot use new on it — only on its children.
Related types sharing code → abstract class.
Practice exercise#
Your task: Create an abstract Account class with a balance and an abstract interestRate(). Create SavingsAccount (4%) and FixedDeposit (7%), and a shared method yearlyInterest() = balance × rate / 100.
Need a hint?
Show solution
class Account {
constructor(balance) {
if (new.target === Account) throw new Error('Account is abstract');
this.balance = balance;
}
interestRate() { throw new Error('not implemented'); }
yearlyInterest() { return (this.balance * this.interestRate()) / 100; }
}
class SavingsAccount extends Account { interestRate() { return 4; } }
class FixedDeposit extends Account { interestRate() { return 7; } }
console.log(new SavingsAccount(10000).yearlyInterest()); // 400
console.log(new FixedDeposit(10000).yearlyInterest()); // 700
Mini quiz#
An abstract class can have a constructor.
Want more? Practise all OOP Concepts MCQs · OOP Concepts interview questions
My notes#
Saved in this browser.