Object-Oriented Programming (OOP) is one of the most widely used programming paradigms in software development. But is also one of the most misunderstood.
This article will help you gain a solid grasp of OOP in TypeScript by walking you through the language features that support it, and then showing how these features naturally give rise to the four foundational principles: inheritance, polymorphism, encapsulation, and abstraction.
Prerequisites
To get the most out of this article, you should be familiar with:
-
JavaScript fundamentals – variables, functions, objects, and arrays.
-
Basic TypeScript syntax – including types and how they differ from plain JavaScript.
Table of Contents
How to Read This Article
I’ve organized this article into two sections. The first section covers TypeScript language features that enable you to implement Object-Oriented Programming (OOP). The second part discusses concepts derived from these features that lead to the four OOP principles: inheritance, polymorphism, encapsulation, and abstraction.
While many teachers, books, and courses start by explaining these principles, I prefer to start with the language features themselves. The reason is simple: they are formal structures – in other words, concrete. Moreover, throughout the article, you’ll notice that the OOP principles naturally emerge when you use the language structure correctly.
TypeScript Language Features
In this section, we’ll explore TypeScript’s features that facilitate OOP implementation. Similar mechanisms exist in other object-oriented languages, such as Java and C#, though they may vary in syntax while preserving the core concepts.
Objects
An object is a data type that stores a collection of values organized into key/value pairs. These may include primitive data or other objects.
In the following example, the person
object stores various pieces of information, such as the key name
, which contains the value "Lucas"
of type string
, and the address
key, which holds another object.
<span class="hljs-keyword">const</span> person = {
name: <span class="hljs-string">"Lucas"</span>, <span class="hljs-comment">// primitive value of type string</span>
surname: <span class="hljs-string">"Garcez"</span>,
age: <span class="hljs-number">28</span>, <span class="hljs-comment">// primitive value of type number</span>
address: {
<span class="hljs-comment">// object type containing the keys "city" and "country"</span>
city: <span class="hljs-string">"Melbourne"</span>,
country: <span class="hljs-string">"Australia"</span>,
},
};
Classes, Attributes, and Methods
A class serves as a blueprint for creating objects. It specifies an object’s structure and behavior through its attributes and methods. Attributes outline the data structure (keys and value types), whereas methods define the actions that can be performed on those attributes.
<span class="hljs-keyword">class</span> Person {
name: <span class="hljs-built_in">string</span>; <span class="hljs-comment">// attribute</span>
surname: <span class="hljs-built_in">string</span>; <span class="hljs-comment">// attribute</span>
age: <span class="hljs-built_in">number</span>; <span class="hljs-comment">// attribute</span>
<span class="hljs-comment">// constructor method (special method)</span>
<span class="hljs-keyword">constructor</span>(<span class="hljs-params">name: <span class="hljs-built_in">string</span>, surname: <span class="hljs-built_in">string</span>, age: <span class="hljs-built_in">number</span></span>) {
<span class="hljs-built_in">this</span>.name = name;
<span class="hljs-built_in">this</span>.surname = surname;
<span class="hljs-built_in">this</span>.age = age;
}
<span class="hljs-comment">// method to obtain the full name: "Lucas Garcez"</span>
getFullName() {
<span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.name}</span> <span class="hljs-subst">${<span class="hljs-built_in">this</span>.surname}</span>`</span>;
}
}
Constructor Method
The constructor is a special method within a class. It’s automatically invoked when a new object is created. Constructors are responsible for initializing the class attributes with values provided during object creation. In TypeScript, the constructor is defined using the constructor
keyword, as you can see in the code above.
Instance
An instance refers to an object created from a class. For example, using the class Person
mentioned above, you can create an object named lucas
. Therefore, lucas
is an instance of the class Person
. To create an instance of an object in JavaScript or TypeScript, you use the keyword new
, as demonstrated below:
<span class="hljs-keyword">const</span> lucas = <span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Lucas"</span>, <span class="hljs-string">"Garcez"</span>, <span class="hljs-number">28</span>);
lucas.name; <span class="hljs-comment">// "Lucas"</span>
lucas.getFullName(); <span class="hljs-comment">// "Lucas Garcez"</span>
It is important to note that you can create multiple objects (instances) from the same class. Although all these objects share the same structure (attributes and methods), they are independent and occupy separate memory spaces within the program.
For instance, when creating a new object:
<span class="hljs-keyword">const</span> maria = <span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Maria"</span>, <span class="hljs-string">"Oliveira"</span>, <span class="hljs-number">19</span>);
You now have a new instance of the Person
class that doesn’t interfere with the previously created lucas
object. Each instance maintains its own values and behaviors, ensuring that manipulating one object doesn’t affect the others.
Interfaces
An interface defines a contract establishing which attributes and methods a class must implement. In TypeScript, this relationship is established using the keyword implements
. When a class implements an interface, it must include all the attributes and methods specified by that interface and their respective types.
In the following example, you have a banking system where a customer can have either CurrentAccount
or SavingsAccount
account. Both options must adhere to the bank’s general account rules defined by the BankAccount
interface.
<span class="hljs-comment">// Contract defining the attributes and methods of a bank account</span>
<span class="hljs-keyword">interface</span> BankAccount {
balance: <span class="hljs-built_in">number</span>;
deposit(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span>;
withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span>;
}
<span class="hljs-keyword">class</span> CurrentAccount <span class="hljs-keyword">implements</span> BankAccount {
balance: <span class="hljs-built_in">number</span>;
<span class="hljs-comment">// The class can have other attributes and methods</span>
<span class="hljs-comment">// beyond those specified in the interface</span>
overdraftLimit: <span class="hljs-built_in">number</span>;
deposit(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-built_in">this</span>.balance += amount;
}
withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-keyword">if</span> (amount <= <span class="hljs-built_in">this</span>.balance) {
<span class="hljs-built_in">this</span>.balance -= amount;
}
}
}
<span class="hljs-keyword">class</span> SavingsAccount <span class="hljs-keyword">implements</span> BankAccount {
balance: <span class="hljs-built_in">number</span>;
deposit(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-comment">// can have different logic from CurrentAccount</span>
<span class="hljs-comment">// but must respect the method signature,</span>
<span class="hljs-comment">// i.e., parameters (amount: number) and return type (void)</span>
}
withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-comment">// ...</span>
}
}
Abstract Classes
Just like interfaces, abstract classes define a model or contract that other classes must follow. But while an interface only describes the structure of a class without providing implementations, an abstract class can include method declarations and concrete implementations.
Unlike regular classes, though, abstract classes cannot be instantiated directly – they exist solely as a base from which other classes can inherit their methods and attributes.
In TypeScript, the abstract
keyword is used to define an abstract class. In the following example, you’ll refactor the banking system by replacing the interface with an abstract class to define base behavior for all bank accounts.
<span class="hljs-comment">// Abstract class that serves as the base for any type of bank account</span>
<span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> BankAccount {
balance: <span class="hljs-built_in">number</span>;
<span class="hljs-keyword">constructor</span>(<span class="hljs-params">initialBalance: <span class="hljs-built_in">number</span></span>) {
<span class="hljs-built_in">this</span>.balance = initialBalance;
}
<span class="hljs-comment">// Concrete method (with implementation)</span>
deposit(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-built_in">this</span>.balance += amount;
}
<span class="hljs-comment">// Abstract method (must be implemented by subclasses)</span>
<span class="hljs-keyword">abstract</span> withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span>;
}
<span class="hljs-keyword">class</span> CurrentAccount <span class="hljs-keyword">extends</span> BankAccount {
withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-keyword">const</span> fee = <span class="hljs-number">2</span>; <span class="hljs-comment">// Current accounts have a fixed withdrawal fee</span>
<span class="hljs-keyword">const</span> totalAmount = amount + fee;
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.balance >= totalAmount) {
<span class="hljs-built_in">this</span>.balance -= totalAmount;
} <span class="hljs-keyword">else</span> {
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Insufficient balance."</span>);
}
}
}
<span class="hljs-keyword">class</span> SavingsAccount <span class="hljs-keyword">extends</span> BankAccount {
withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.balance >= amount) {
<span class="hljs-built_in">this</span>.balance -= amount;
} <span class="hljs-keyword">else</span> {
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Insufficient balance."</span>);
}
}
}
<span class="hljs-comment">// ❌ Error! Cannot instantiate an abstract class</span>
<span class="hljs-keyword">const</span> genericAccount = <span class="hljs-keyword">new</span> BankAccount(<span class="hljs-number">1000</span>); <span class="hljs-comment">// Error</span>
<span class="hljs-comment">// ✅ Creating a current account</span>
<span class="hljs-keyword">const</span> currentAccount = <span class="hljs-keyword">new</span> CurrentAccount(<span class="hljs-number">2000</span>); <span class="hljs-comment">// uses the BankAccount constructor</span>
currentAccount.deposit(<span class="hljs-number">500</span>); <span class="hljs-comment">// uses the deposit method from BankAccount</span>
currentAccount.withdraw(<span class="hljs-number">300</span>); <span class="hljs-comment">// uses the withdraw method from CurrentAccount</span>
<span class="hljs-comment">// ✅ Creating a savings account</span>
<span class="hljs-keyword">const</span> savingsAccount = <span class="hljs-keyword">new</span> SavingsAccount(<span class="hljs-number">1500</span>); <span class="hljs-comment">// uses the BankAccount constructor</span>
savingsAccount.deposit(<span class="hljs-number">1100</span>); <span class="hljs-comment">// uses the deposit method from BankAccount</span>
savingsAccount.withdraw(<span class="hljs-number">500</span>); <span class="hljs-comment">// uses the withdraw method from SavingsAccount</span>
Object-Oriented Programming Principles
Now that you understand the key language mechanisms, you can formalize the pillars of Object-Oriented Programming that guide the creation of systems that are better organized, reusable, and scalable.
Inheritance – Superclass and Subclass
Inheritance is a mechanism that allows a class to derive characteristics from another class. When a class B
inherits from a class A
, it means that B
automatically acquires the attributes and methods of A
without needing to redefine them.
You can visualize this relationship as a parent-child structure, where A
is the superclass (base/parent class) and B
is the subclass (derived/child class). A subclass can use inherited resources, add new behaviors, or override superclass methods to address specific needs.
We’ve already discussed inheritance when learning about abstract classes, but inheritance can also be applied to concrete classes. This allows for code reuse and behavior specialization.
<span class="hljs-comment">// BankAccount is now a regular class where you define attributes and methods</span>
<span class="hljs-comment">// that will be reused by the child class CurrentAccount</span>
<span class="hljs-keyword">class</span> BankAccount {
balance: <span class="hljs-built_in">number</span> = <span class="hljs-number">0</span>;
<span class="hljs-keyword">constructor</span>(<span class="hljs-params">initialBalance: <span class="hljs-built_in">number</span></span>) {
<span class="hljs-built_in">this</span>.balance = initialBalance;
}
deposit(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-built_in">this</span>.balance += amount;
}
withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-keyword">if</span> (amount <= <span class="hljs-built_in">this</span>.balance) {
<span class="hljs-built_in">this</span>.balance -= amount;
}
}
}
<span class="hljs-comment">// CurrentAccount is a subclass of BankAccount, meaning </span>
<span class="hljs-comment">// it inherits its attributes and methods.</span>
<span class="hljs-keyword">class</span> CurrentAccount <span class="hljs-keyword">extends</span> BankAccount {
overdraftLimit: <span class="hljs-built_in">number</span>; <span class="hljs-comment">// new attribute exclusive to CurrentAccount</span>
<span class="hljs-comment">// When specifying a constructor method for a subclass,</span>
<span class="hljs-comment">// we need to call another special method, "super".</span>
<span class="hljs-comment">// This method calls the superclass (BankAccount) constructor to ensure</span>
<span class="hljs-comment">// it is initialized before creating the CurrentAccount object itself.</span>
<span class="hljs-keyword">constructor</span>(<span class="hljs-params">initialBalance: <span class="hljs-built_in">number</span>, overdraftLimit: <span class="hljs-built_in">number</span></span>) {
<span class="hljs-built_in">super</span>(initialBalance); <span class="hljs-comment">// Must match the superclass constructor method signature</span>
<span class="hljs-built_in">this</span>.overdraftLimit = overdraftLimit;
}
<span class="hljs-comment">// Even though the withdraw method already exists in the superclass (BankAccount),</span>
<span class="hljs-comment">// it is overridden here. This means every time a CurrentAccount</span>
<span class="hljs-comment">// object calls the withdraw method, this implementation will be used, </span>
<span class="hljs-comment">// ignoring the superclass method.</span>
override withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-keyword">const</span> totalAvailable = <span class="hljs-built_in">this</span>.balance + <span class="hljs-built_in">this</span>.overdraftLimit;
<span class="hljs-keyword">if</span> (amount > <span class="hljs-number">0</span> && amount <= totalAvailable) {
<span class="hljs-built_in">this</span>.balance -= amount;
}
}
}
<span class="hljs-comment">// Creating a CurrentAccount with an initial balance of $0.00</span>
<span class="hljs-comment">// and an overdraft limit of $100.</span>
<span class="hljs-keyword">const</span> currentAccount = <span class="hljs-keyword">new</span> CurrentAccount(<span class="hljs-number">0</span>, <span class="hljs-number">100</span>);
<span class="hljs-comment">// Making a $200 deposit by calling the deposit method</span>
<span class="hljs-comment">// In this case, the method from BankAccount will be invoked</span>
<span class="hljs-comment">// since deposit was not overridden in CurrentAccount</span>
currentAccount.deposit(<span class="hljs-number">200</span>); <span class="hljs-comment">// balance: 200</span>
<span class="hljs-comment">// Withdrawing $250 by calling the withdraw method</span>
<span class="hljs-comment">// In this case, the method from CurrentAccount will be invoked</span>
<span class="hljs-comment">// as it has been overridden in its definition</span>
currentAccount.withdraw(<span class="hljs-number">250</span>); <span class="hljs-comment">// balance: -50</span>
Polymorphism
Polymorphism is a concept that often creates confusion in Object-Oriented Programming. But in practice, it is merely a natural consequence of using interfaces and inheritance.
The term polymorphism originates from Greek and means “many forms” (poly = many, morphos = forms). This concept allows objects from different classes to respond to the same method call but with distinct implementations, making code more flexible and reusable.
To clarify this concept, let’s consider a practical example. Suppose you have a function named sendMoney
, responsible for processing a financial transaction, transferring a certain amount from account A to account B. The only requirement is that both accounts follow a common contract, ensuring the methods withdraw
and deposit
are available.
<span class="hljs-comment">// BankAccount could be an interface, a concrete class,</span>
<span class="hljs-comment">// or an abstract class. For the sendMoney function, the specific implementation</span>
<span class="hljs-comment">// does not matter—only that BankAccount includes withdraw and deposit methods.</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendMoney</span>(<span class="hljs-params">
sender: BankAccount,
receiver: BankAccount,
amount: <span class="hljs-built_in">number</span>
</span>) </span>{
sender.withdraw(amount);
receiver.deposit(amount);
}
<span class="hljs-keyword">const</span> lucasAccount = <span class="hljs-keyword">new</span> CurrentAccount(<span class="hljs-number">500</span>, <span class="hljs-number">200</span>);
<span class="hljs-keyword">const</span> mariaAccount = <span class="hljs-keyword">new</span> SavingsAccount(<span class="hljs-number">300</span>);
<span class="hljs-comment">// transferring $100 from Lucas to Maria</span>
sendMoney(lucasAccount, mariaAccount, <span class="hljs-number">100</span>);
Polymorphic Methods:
The withdraw
and deposit
methods are called within the sendMoney
function without requiring the function to know whether it is dealing with a CurrentAccount
or SavingsAccount
. Each class implements withdraw
according to its own rules, demonstrating the concept of polymorphism.
Decoupling:
The sendMoney
function does not depend on the specific type of bank account. Any class that extends BankAccount
(if it’s a class) or implements BankAccount
(if it’s an interface) can be used without requiring modifications to the sendMoney
function.
With this approach, you ensure flexibility and code reusability, as new account types can be introduced without affecting the functionality of sendMoney
.
Encapsulation
Encapsulation is one of the fundamental principles of OOP, but its concept can be applied to any programming paradigm. It involves hiding the internal implementation details of a module, class, function, or any other software component, exposing only what is necessary for external use. This improves code security, maintainability, and modularity by preventing unauthorized access and ensuring controlled interactions.
Access Modifiers – public
, private
, and protected
In OOP, encapsulation is essential for controlling the visibility and access to methods and attributes within a class. In TypeScript, this is achieved using access modifiers, which are defined by the keywords public
, protected
, and private
.
-
public
– Allows the attribute or method to be accessed from anywhere, both inside and outside the class. This is the default visibility, meaning that if no access modifier is specified in the code, TypeScript assumes it aspublic
. -
protected
– Allows access within the class and its subclasses but prevents external access. -
private
– Restricts access to the attribute or method only within the class itself.
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Person {
<span class="hljs-keyword">private</span> firstName: <span class="hljs-built_in">string</span>; <span class="hljs-comment">// Accessible only within the class itself</span>
<span class="hljs-keyword">private</span> lastName: <span class="hljs-built_in">string</span>; <span class="hljs-comment">// Accessible only within the class itself</span>
<span class="hljs-keyword">protected</span> birthDate: <span class="hljs-built_in">Date</span>; <span class="hljs-comment">// Accessible by subclasses but not from outside</span>
<span class="hljs-keyword">constructor</span>(<span class="hljs-params">firstName: <span class="hljs-built_in">string</span>, lastName: <span class="hljs-built_in">string</span>, birthDate: <span class="hljs-built_in">Date</span></span>) {
<span class="hljs-built_in">this</span>.firstName = firstName;
<span class="hljs-built_in">this</span>.lastName = lastName;
<span class="hljs-built_in">this</span>.birthDate = birthDate;
}
<span class="hljs-comment">// Public method that can be accessed from anywhere</span>
<span class="hljs-keyword">public</span> getFullName(): <span class="hljs-built_in">string</span> {
<span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.firstName}</span> <span class="hljs-subst">${<span class="hljs-built_in">this</span>.lastName}</span>`</span>;
}
}
<span class="hljs-comment">// The Professor class inherits from Person and can access</span>
<span class="hljs-comment">// attributes and methods according to their access modifiers.</span>
<span class="hljs-keyword">class</span> Professor <span class="hljs-keyword">extends</span> Person {
<span class="hljs-keyword">constructor</span>(<span class="hljs-params">firstName: <span class="hljs-built_in">string</span>, lastName: <span class="hljs-built_in">string</span>, birthDate: <span class="hljs-built_in">Date</span></span>) {
<span class="hljs-built_in">super</span>(firstName, lastName, birthDate); <span class="hljs-comment">// Calls the superclass (Person) constructor</span>
}
getProfile() {
<span class="hljs-built_in">this</span>.birthDate; <span class="hljs-comment">// ✅ Accessible because it is protected</span>
<span class="hljs-built_in">this</span>.getFullName(); <span class="hljs-comment">// ✅ Accessible because it is public</span>
<span class="hljs-built_in">this</span>.firstName; <span class="hljs-comment">// ❌ Error! Cannot be accessed because it is private in the Person class</span>
<span class="hljs-built_in">this</span>.lastName; <span class="hljs-comment">// ❌ Error! Cannot be accessed because it is private in the Person class</span>
}
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-comment">// Creating an instance of Professor</span>
<span class="hljs-keyword">const</span> lucas = <span class="hljs-keyword">new</span> Professor(<span class="hljs-string">"Lucas"</span>, <span class="hljs-string">"Garcez"</span>, <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-string">"1996-02-06"</span>));
<span class="hljs-comment">// Testing direct access to attributes and methods</span>
lucas.birthDate; <span class="hljs-comment">// ❌ Error! birthDate is protected and can only be accessed within the class or subclasses</span>
lucas.getFullName(); <span class="hljs-comment">// ✅ Accessible because it is a public method</span>
lucas.firstName; <span class="hljs-comment">// ❌ Error! firstName is private and cannot be accessed outside the Person class</span>
lucas.lastName; <span class="hljs-comment">// ❌ Error! lastName is also private and inaccessible outside the Person class</span>
}
Access Modifiers Table
Modifier | Access within the class | Access in subclass | Access outside the class |
public |
✅ Yes | ✅ Yes | ✅ Yes |
protected |
✅ Yes | ✅ Yes | ❌ No |
private |
✅ Yes | ❌ No | ❌ No |
Abstraction
The concept of abstraction frequently causes confusion because its meaning goes beyond the technical context. If you look up the definition of the word in English, the Cambridge Dictionary defines “abstract” as:
Something that exists as an idea, feeling, or quality, rather than as a material object.
This definition can be directly applied to OOP: Abstraction represents an idea or concept without going into concrete implementation details.
Many online references describe abstraction as “hiding implementation details,” which can be misleading since this concept is more closely related to encapsulation. In OOP, abstraction does NOT mean hiding details but defining contracts through abstract classes and interfaces.
<span class="hljs-comment">// Abstraction using interface</span>
<span class="hljs-keyword">interface</span> BankAccountInterface {
balance: <span class="hljs-built_in">number</span>;
deposit(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span>;
withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span>;
}
<span class="hljs-comment">// Abstraction using class</span>
<span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> BankAccountClass {
balance: <span class="hljs-built_in">number</span>;
<span class="hljs-keyword">constructor</span>(<span class="hljs-params">initialBalance: <span class="hljs-built_in">number</span></span>) {
<span class="hljs-built_in">this</span>.balance = initialBalance;
}
<span class="hljs-comment">// Concrete method (with implementation)</span>
deposit(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
<span class="hljs-built_in">this</span>.balance += amount;
}
<span class="hljs-comment">// Abstract method (must be implemented by subclasses)</span>
<span class="hljs-keyword">abstract</span> withdraw(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span>;
}
In the examples above, both BankAccountInterface
and BankAccountClass
are examples of abstraction as they define contracts that must be implemented by those who use them.
Conclusion
Although learning Object-Oriented Programming isn’t easy, I hope this article has helped clarify the OOP fundamentals and advanced topics.
If you want to keep learning TypeScript and OOP, I highly recommend reading Martin Fowler’s book Refactoring: Improving the Design of Existing Code. This book contains a massive catalog of refactoring techniques, and the second edition has all code examples written in TypeScript, many of which use OOP features and principles mentioned here.
Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & MoreÂ