While I’m working on a web application, I needed a way to manage users, including storing their names, emails, and roles, and updating their details as required.
Instead of writing the same code again and again for each user, I decided to use a TypeScript class. It helped me organize the code, add useful methods, and maintain a clean and reusable codebase.
In this tutorial, I will explain everything you need to know about TypeScript classes with practical examples. Whether you’re a beginner or looking to refine your skills, you’ll find valuable insights here.
What is a Class in TypeScript?
A TypeScript class is a blueprint for creating objects with pre-defined properties and methods. It follows the Object-Oriented Programming (OOP) paradigm, enabling you to implement concepts such as inheritance, encapsulation, and polymorphism.
Classes help structure your code better, making it more maintainable and scalable in large applications.
Basic TypeScript Class Syntax
Let’s start with a simple example of a TypeScript class:
class Customer {
// Properties
id: number;
name: string;
email: string;
// Constructor
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
// Method
displayInfo(): string {
return `Customer: ${this.name}, Email: ${this.email}`;
}
}
// Creating an instance
const customer1 = new Customer(1, "John Smith", "john@example.com");
console.log(customer1.displayInfo()); // Output: Customer: John Smith, Email: john@example.comIn this example, I’ve created a Customer class with three properties, a constructor to initialize those properties, and a method to display customer information.

Access Modifiers in TypeScript Classes
TypeScript provides three access modifiers to control the visibility of class members:
1. Public
Public members are accessible from anywhere. This is the default if no modifier is specified.
class Product {
public name: string;
constructor(name: string) {
this.name = name;
}
}
const product = new Product("iPhone");
console.log(product.name); // AccessibleOutput:

2. Private
Private members are only accessible within the class itself.
class BankAccount {
private balance: number;
constructor(initialBalance: number) {
this.balance = initialBalance;
}
deposit(amount: number): void {
this.balance += amount;
}
getBalance(): number {
return this.balance;
}
}
const account = new BankAccount(1000);
// console.log(account.balance); // Error: Property 'balance' is private
console.log(account.getBalance()); // Works: 1000Output:

3. Protected
Protected members are accessible within the class and its subclasses.
class Person {
protected ssn: string;
public name: string;
constructor(ssn: string, name: string) {
this.ssn = ssn;
this.name = name;
}
}
class Employee extends Person {
private employeeId: number;
constructor(ssn: string, name: string, id: number) {
super(ssn, name);
this.employeeId = id;
}
getSSN(): string {
return this.ssn; // Accessible because ssn is protected
}
}
const emp = new Employee("123-45-6789", "Alice Johnson", 12345);
// console.log(emp.ssn); // Error: Property 'ssn' is protected
console.log(emp.getSSN()); // Works: 123-45-6789Output:

Constructor in TypeScript Classes
TypeScript offers a concise way to define and initialize class properties in the constructor:
class Car {
constructor(
public make: string,
public model: string,
private vin: string,
protected year: number
) {}
getInfo(): string {
return `${this.year} ${this.make} ${this.model}`;
}
}
const myCar = new Car("Tesla", "Model 3", "5YJ3E1EA7JF000123", 2023);
console.log(myCar.getInfo()); // Output: 2023 Tesla Model 3This shorthand syntax automatically creates and initializes the properties with the given values, saving you from writing repetitive code.

Implementing Interfaces in TypeScript
Classes in TypeScript can implement interfaces, ensuring they adhere to a specific contract:
interface Taxable {
calculateTax(): number;
}
class Income implements Taxable {
constructor(private amount: number, private taxRate: number) {}
calculateTax(): number {
return this.amount * this.taxRate;
}
}
class Property implements Taxable {
constructor(private value: number, private propertyTaxRate: number) {}
calculateTax(): number {
return this.value * this.propertyTaxRate;
}
}
// Using these classes
const salary = new Income(85000, 0.24);
console.log(`Tax on income: $${salary.calculateTax()}`); // Tax on income: $20400
const house = new Property(350000, 0.011);
console.log(`Property tax: $${house.calculateTax()}`); // Property tax: $3850Output:

Abstract Classes in TypeScript
Abstract classes serve as base classes that cannot be instantiated directly but can be extended by other classes:
abstract class Shape {
constructor(protected color: string) {}
abstract calculateArea(): number;
displayColor(): string {
return `This shape is ${this.color}`;
}
}
class Circle extends Shape {
constructor(color: string, private radius: number) {
super(color);
}
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
class Rectangle extends Shape {
constructor(color: string, private width: number, private height: number) {
super(color);
}
calculateArea(): number {
return this.width * this.height;
}
}
// Using these classes
const circle = new Circle("red", 5);
console.log(circle.displayColor()); // This shape is red
console.log(`Area: ${circle.calculateArea().toFixed(2)} square units`); // Area: 78.54 square units
const rectangle = new Rectangle("blue", 10, 5);
console.log(`Area: ${rectangle.calculateArea()} square units`); // Area: 50 square unitsOutput:

Static Members in TypeScript Classes
Static properties and methods belong to the class itself, not to instances of the class:
class MathUtils {
static PI: number = 3.14159;
static square(x: number): number {
return x * x;
}
static calculateCircumference(radius: number): number {
return 2 * MathUtils.PI * radius;
}
}
// Using static members without creating an instance
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.square(4)); // 16
console.log(MathUtils.calculateCircumference(5)); // 31.4159Output:

Real-World Class Example in TypeScript: E-Commerce System
Let’s create a more complex example that models part of an e-commerce system:
// Product interface
interface IProduct {
id: number;
name: string;
price: number;
getDiscountedPrice(discountPercent: number): number;
}
// Abstract Product class
abstract class Product implements IProduct {
constructor(
public id: number,
public name: string,
public price: number,
protected category: string
) {}
abstract getDiscountedPrice(discountPercent: number): number;
getInfo(): string {
return `${this.name} - $${this.price} (${this.category})`;
}
}
// Concrete product classes
class PhysicalProduct extends Product {
constructor(
id: number,
name: string,
price: number,
category: string,
private weightInKg: number,
private dimensions: {length: number, width: number, height: number}
) {
super(id, name, price, category);
}
getDiscountedPrice(discountPercent: number): number {
return this.price * (1 - discountPercent / 100);
}
getShippingCost(): number {
return this.weightInKg * 2.5; // $2.50 per kg
}
}
class DigitalProduct extends Product {
constructor(
id: number,
name: string,
price: number,
category: string,
private fileSizeInMb: number,
private downloadLink: string
) {
super(id, name, price, category);
}
getDiscountedPrice(discountPercent: number): number {
// Digital products have higher discount
return this.price * (1 - (discountPercent + 5) / 100);
}
getDownloadInfo(): string {
return `Download from: ${this.downloadLink} (${this.fileSizeInMb} MB)`;
}
}
// Cart class to manage products
class ShoppingCart {
private items: IProduct[] = [];
addItem(product: IProduct): void {
this.items.push(product);
}
removeItem(productId: number): void {
this.items = this.items.filter(item => item.id !== productId);
}
getTotalPrice(): number {
return this.items.reduce((total, item) => total + item.price, 0);
}
getDiscountedTotal(discountPercent: number): number {
return this.items.reduce(
(total, item) => total + item.getDiscountedPrice(discountPercent),
0
);
}
}
// Usage example
const laptop = new PhysicalProduct(
1,
"MacBook Pro",
1299.99,
"Electronics",
2.1,
{length: 30.41, width: 21.24, height: 1.56}
);
const ebook = new DigitalProduct(
2,
"TypeScript Mastery",
24.99,
"Books",
15.4,
"https://example.com/downloads/ts-mastery"
);
console.log(laptop.getInfo()); // MacBook Pro - $1299.99 (Electronics)
console.log(`Shipping cost: $${laptop.getShippingCost().toFixed(2)}`); // Shipping cost: $5.25
console.log(ebook.getInfo()); // TypeScript Mastery - $24.99 (Books)
console.log(ebook.getDownloadInfo()); // Download from: https://example.com/downloads/ts-mastery (15.4 MB)
// Using shopping cart
const cart = new ShoppingCart();
cart.addItem(laptop);
cart.addItem(ebook);
console.log(`Total price: $${cart.getTotalPrice().toFixed(2)}`); // Total price: $1324.98
console.log(`Discounted total (10%): $${cart.getDiscountedTotal(10).toFixed(2)}`); // Discounted total (10%): $1177.74This example demonstrates how TypeScript classes can be used to create a complex system with inheritance, abstract classes, interfaces, and various access modifiers.

Using Getters and Setters in TypeScript Classes
TypeScript supports getters and setters for controlled access to class properties:
class Temperature {
private _celsius: number;
constructor(celsius: number) {
this._celsius = celsius;
}
// Getter for Celsius
get celsius(): number {
return this._celsius;
}
// Setter for Celsius
set celsius(value: number) {
if (value < -273.15) {
throw new Error("Temperature cannot be below absolute zero");
}
this._celsius = value;
}
// Getter for Fahrenheit
get fahrenheit(): number {
return this._celsius * 9/5 + 32;
}
// Setter for Fahrenheit
set fahrenheit(value: number) {
this.celsius = (value - 32) * 5/9;
}
}
const temp = new Temperature(25);
console.log(`${temp.celsius}°C`); // 25°C
console.log(`${temp.fahrenheit}°F`); // 77°F
temp.fahrenheit = 86;
console.log(`${temp.celsius}°C`); // 30°C
// This would throw an error
// temp.celsius = -300;In this weather-related example, I’ve implemented getters and setters to control how temperature values are accessed and modified. The setter for celsius ensures that temperatures can’t go below absolute zero, adding validation that would be absent with direct property access.

Inheritance in TypeScript Classes
Inheritance allows a class to extend another class, inheriting its properties and methods:
class Vehicle {
constructor(
protected make: string,
protected model: string,
protected year: number
) {}
getInfo(): string {
return `${this.year} ${this.make} ${this.model}`;
}
startEngine(): string {
return "Engine started";
}
}
class ElectricVehicle extends Vehicle {
constructor(
make: string,
model: string,
year: number,
private batteryCapacity: number,
private range: number
) {
super(make, model, year);
}
startEngine(): string {
return "Electric motor activated silently";
}
getBatteryInfo(): string {
return `Battery: ${this.batteryCapacity} kWh, Range: ${this.range} miles`;
}
chargeBattery(percent: number): void {
console.log(`Charging battery to ${percent}%`);
}
}
const teslaModelS = new ElectricVehicle("Tesla", "Model S", 2023, 100, 405);
console.log(teslaModelS.getInfo()); // 2023 Tesla Model S
console.log(teslaModelS.startEngine()); // Electric motor activated silently
console.log(teslaModelS.getBatteryInfo()); // Battery: 100 kWh, Range: 405 miles
teslaModelS.chargeBattery(90); // Charging battery to 90%In this example, I’ve created a base Vehicle class and extended it with an ElectricVehicle class that adds specific properties and methods relevant to electric vehicles. The ElectricVehicle class also overrides the startEngine method with its own implementation.

Readonly Properties in TypeScript
TypeScript allows you to mark properties as readonly, meaning they can only be set during initialization:
class User {
readonly id: number;
name: string;
readonly createdAt: Date;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
this.createdAt = new Date();
}
updateName(newName: string): void {
this.name = newName;
// This would cause an error:
// this.id = 100;
}
}
const user = new User(42, "Jane Doe");
console.log(`User ${user.id}: ${user.name}, created on ${user.createdAt.toLocaleDateString()}`);
user.updateName("Jane Smith");
console.log(`Updated name: ${user.name}`);
// This would cause an error:
// user.id = 100;In this example, the id and createdAt properties are readonly, meaning they can’t be changed after the object is created.

Singleton Pattern Using Static Members in TypeScript
The Singleton pattern ensures that a class has only one instance throughout the application:
class DatabaseConnection {
private static instance: DatabaseConnection;
private connectionString: string;
private constructor(connectionString: string) {
this.connectionString = connectionString;
console.log(`Connecting to database: ${connectionString}`);
}
public static getInstance(connectionString: string): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection(connectionString);
}
return DatabaseConnection.instance;
}
query(sql: string): void {
console.log(`Executing query on ${this.connectionString}: ${sql}`);
}
}
// Using the singleton
const db1 = DatabaseConnection.getInstance("mysql://localhost:3306/myapp");
db1.query("SELECT * FROM users");
// This won't create a new connection, it returns the existing instance
const db2 = DatabaseConnection.getInstance("mysql://localhost:3306/myapp");
db2.query("SELECT * FROM products");
// Both are the same instance
console.log(db1 === db2); // trueIn this example, I’ve implemented a singleton pattern for a database connection. No matter how many times you call getInstance(), you’ll always get back the same instance, which is useful for resources that should be shared across your application.

Class Expressions in TypeScript
Similar to function expressions, TypeScript also supports class expressions:
const Point = class {
constructor(public x: number, public y: number) {}
getDistance(point: InstanceType<typeof Point>): number {
const dx = this.x - point.x;
const dy = this.y - point.y;
return Math.sqrt(dx * dx + dy * dy);
}
};
const p1 = new Point(0, 0);
const p2 = new Point(3, 4);
console.log(`Distance: ${p1.getDistance(p2)}`); // Distance: 5Class expressions can be useful when you need to create a class dynamically or within a specific scope.

Conclusion
TypeScript classes are a powerful tool in your development arsenal. They help you structure your code in a clean and organized manner, providing type safety and enabling object-oriented design patterns.
Whether you’re building a simple utility or a complex enterprise application, understanding how to use TypeScript classes effectively will make your code more robust and maintainable. The examples in this article demonstrate how classes can be applied to real-world scenarios that you might encounter in your development career.
You may like to read:
- TypeScript Classes and Interfaces
- Use TypeScript Enums in Classes
- Extend Interfaces with Classes in TypeScript
- Merge Objects in TypeScript

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.