TypeScript classes are JavaScript classes plus compile-time guarantees: access modifiers, readonly fields, abstract members and interfaces that a class must actually satisfy.
Write a class with typed fields, a constructor and methods
Use public, private, protected and readonly to control what other code may touch
Skip constructor boilerplate with parameter properties
Model a shared contract with abstract classes and with implements
Add static members and get/set accessors to a class
01
Class basics: fields, constructor, methods
✓
A TypeScript class is a JavaScript class with types layered on top. You still write class, constructor and methods exactly as in JavaScript — the only new thing is annotating what type each field, parameter and return value is, so the compiler can catch a wrong assignment before the code ever runs.
Field declarations — title: string at the top of the class body declares a property and its type, before it is ever assigned.
Constructor — runs once, when new ClassName(...) is called, and is the usual place to assign fields from the arguments it receives.
Methods — ordinary functions attached to the class, with typed parameters and a typed return value like any other function.
typescriptEdit it. ⌘/Ctrl + Enter runs.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Book {
title: string
author: string
constructor(title: string, author: string){this.title = title
this.author = author
}describe(): string {return`"${this.title}" by ${this.author}`}}const b =new Book("Dune","Frank Herbert")console.log(b.describe())
You should see
"Dune" by Frank Herbert
Your turn
Add a third field, year: number, assign it in the constructor, and include it in the string describe() returns.
Field declarations are optional if you assign in the constructor
With strict mode on, a field like title: string must either have a default value, be assigned in every constructor path, or be declared with the parameter-property shorthand you will see later in this module. Declare-then-assign, as above, is the pattern to reach for first — it is explicit about the shape of the class before you read a single line of the constructor.
Class
A blueprint for creating objects that share the same fields and methods.
Instance
One object created from a class with new — each call to new Book(...) makes a separate instance.
Field
A typed property declared on the class, one per instance.
Method
A function attached to the class, called on an instance with instance.method().
02
Access modifiers and readonly fields
✓
By default every field and method on a TypeScript class is public — readable and callable from anywhere. Three modifiers narrow that: private restricts a member to the class it is declared in, protected extends that same restriction to subclasses, and readonly allows a field to be set once (at declaration or inside the constructor) and never reassigned after that.
Modifier
Visible from
Typical use
public (default)
Anywhere
The class’s normal API — methods and fields other code is meant to use.
private
Only inside the same class
Internal state a caller has no business touching, like a cache or a counter.
protected
The class and its subclasses
State or helpers a base class exposes to children but not to the outside world.
readonly
Set once, read anywhere it is otherwise visible
Values that must never change after construction, like an ID or a creation date.
typescriptEdit it. ⌘/Ctrl + Enter runs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Animal {protected name: string
constructor(name: string){this.name = name
}speak(): string {return`${this.name} makes a sound`}}class Dog extends Animal {constructor(name: string,public breed: string){super(name)}speak(): string {return`${this.name} barks`}}const d =new Dog("Rex","Labrador")console.log(d.speak())console.log(d.breed)
You should see
Rex barks
Labrador
Your turn
Try adding console.log(d.name) after the existing lines. It still runs here (this editor only strips types, it does not type-check), but paste the same code into the TypeScript Playground and read the error protected produces from outside the class.
A readonly field can still be mutated internally through a method that changes some other field — only reassigning accountNumber itself, from anywhere, is blocked. Confirm that by trying acc.accountNumber = "AC9999" in the compiler.
private and protected are erased at compile time
None of these modifiers exist in the JavaScript TypeScript emits — a private field is an ordinary, fully readable property once compiled. The check is entirely the compiler refusing to let your own code reference it incorrectly; anyone with the compiled .js file (or a debugger) can still read it. If you need privacy enforced at runtime, use JavaScript’s native #field syntax instead.
Error you will hit
TS2341: accessing a private field from outside the class
typescript
12345
class Wallet {private balance =0}const w =new Wallet()console.log(w.balance)
app.ts:5:15 - error TS2341: Property 'balance' is private and only accessible within class 'Wallet'.
5 console.log(w.balance)
~~~~~~~
Why the compiler said that
balance is declared private, which restricts it to code written inside Wallet itself. The call on line 5 is outside the class, so the compiler refuses to let it read the field, even though at runtime the property is perfectly readable.
The fix
Expose a public method that returns the value instead of the field itself.
typescript
12345678
class Wallet {private balance =0getBalance(): number {returnthis.balance
}}const w =new Wallet()console.log(w.getBalance())
JuniorWhat happens to TypeScript's private and protected keywords when the code compiles to JavaScript?
They are erased. tsc enforces access rules only while checking your TypeScript source; the emitted JavaScript has an ordinary property that anything can read or write. Real, runtime-enforced privacy requires the native #field syntax instead.
What they are really testing: Whether the candidate understands that private is a compile-time-only guarantee, not a runtime one.
Quick check
In the compiled JavaScript output, which TypeScript access modifier still restricts access at runtime?
public, private and protected are purely compile-time constructs, erased when tsc emits JavaScript. Only the native #field syntax is actually private at runtime.
03
Parameter properties: the constructor shorthand
✓
Declaring a field and then assigning it in the constructor is common enough that TypeScript has a shortcut: put an access modifier (public, private, protected or readonly) directly in front of a constructor parameter, and the compiler both declares a field of that name AND assigns the argument to it — one line does what would otherwise take two.
Without parameter properties
class Employee {
name: string
private salary: number
constructor(name: string, salary: number) {
this.name = name
this.salary = salary
}
}
With parameter properties
class Employee {
constructor(
public name: string,
private salary: number,
) {}
}
typescriptEdit it. ⌘/Ctrl + Enter runs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Employee {constructor(public name: string,private salary: number,readonly employeeId: string,){}raise(amount: number):void{this.salary += amount
}getSalary(): number {returnthis.salary
}}const e =new Employee("Nia",60000,"EMP-204")
e.raise(5000)console.log(e.name, e.employeeId, e.getSalary())
You should see
Nia EMP-204 65000
Your turn
Add a fourth parameter property, public department: string = "General", with a default value, and print it too.
VisualizeWhat a parameter property actually does at the call siteStep 1 / 5
1class Employee {
2 constructor(
3 public name: string,
4 private salary: number,
5){}
6raise(amount: number): void {
7 this.salary += amount
8}
9}
10const e = new Employee("Nia",60000)
11e.raise(5000)
12console.log(e.name)
Line 10
new Employee("Nia", 60000) runs the constructor. Because name and salary each carry a modifier keyword, the compiler also generates this.name = name and this.salary = salary inside the constructor body, ahead of anything you wrote there yourself.
Variables now
name
'Nia'
salary
60000
All 5 steps as a table
Step
Line
What happened
Variables now
1
10
new Employee("Nia", 60000) runs the constructor. Because name and salary each carry a modifier keyword, the compiler also generates this.name = name and this.salary = salary inside the constructor body, ahead of anything you wrote there yourself.
name = 'Nia'salary = 60000
2
3
public name: string is the field declaration and the assignment in one place: e.name is now set.
e.name = 'Nia'
3
4
private salary: number does the same, but the field it created is only reachable from inside Employee after the compiler checks your code — at runtime it is a completely ordinary property.
e.salary = 60000
4
11
e.raise(5000) runs, adding 5000 to the private field.
e.salary = 65000
5
12
Prints the public field.
Error you will hit
TS2564: a field with no initializer and no assignment
typescript
123456
class Order {
total: number
constructor(items: number[]){console.log(items.length)}}
app.ts:2:3 - error TS2564: Property 'total' has no initializer and is not definitely assigned in the constructor.
2 total: number
~~~~~
Why the compiler said that
Under strict mode, every declared field must be assigned before the constructor finishes running — either with a default value or in every code path through the constructor. total is declared but never touched, so the compiler cannot prove it will hold a number by the time an Order exists.
The fix
Give the field a default value, or assign it inside the constructor.
typescript
123456
class Order {
total: number =0constructor(items: number[]){this.total = items.reduce((sum, n)=> sum + n,0)}}
Mid-levelWhat does constructor(private x: number) do differently from writing constructor(x: number) { this.x = x } yourself?
Nothing behaviorally — it is pure shorthand. A modifier keyword in front of a constructor parameter tells the compiler to both declare a class field of that name with that access level, and assign the incoming argument to it, automatically, before the rest of the constructor body runs. The emitted JavaScript is the same either way.
What they are really testing: Familiarity with parameter properties, and whether the candidate knows it is sugar, not a different mechanism.
04
Abstract classes and abstract methods
✓
An abstract class is a base class that can never be instantiated directly — only extended. It can mix abstract methods, which declare a signature but no body and must be implemented by every subclass, with ordinary concrete methods that subclasses inherit as-is. This is the classic template method pattern: the base class defines the overall shape, subclasses fill in the missing piece.
Add a second subclass, BankTransfer, implementing charge() differently, and call .receipt(75) on an instance of it too.
Abstract class vs. plain base class
A plain base class can be instantiated on its own, even if that rarely makes sense. Marking it abstract makes that a compile error — PaymentMethod above is a contract that promises charge() exists, not a payment method you would ever create by itself.
app.ts:4:15 - error TS2511: Cannot create an instance of an abstract class.
4 const pm = new PaymentMethod()
~~~~~~~~~~~~~
Why the compiler said that
PaymentMethod is declared abstract specifically to forbid this — an abstract class describes a shape a subclass must complete, and charge() has no body here to run.
The fix
Instantiate a concrete subclass instead, one that actually implements charge().
typescript
123456
class CreditCard extends PaymentMethod {charge(amount: number): string {return`credit card for $${amount}`}}const pm =new CreditCard()
Mid-levelWhen would you reach for an abstract class instead of an interface?
When subclasses need to share real implementation — a base constructor, fields, or concrete helper methods — in addition to a contract. An interface is a pure shape with zero implementation of its own; an abstract class can mix abstract members (subclasses must implement) with concrete ones (subclasses inherit unchanged), which is exactly what a template method pattern needs.
What they are really testing: Whether the candidate can explain the practical difference, not just recite the syntax.
05
implements: a class fulfilling an interface
✓
An interface describes a shape with no implementation at all — just the fields and method signatures a type must have. A class can declare implements SomeInterface, and the compiler checks that the class really does provide every member the interface lists, with a compatible type for each one.
app.ts:4:7 - error TS2420: Class 'Product' incorrectly implements interface 'Serializable'.
Property 'serialize' is missing in type 'Product' but required in type 'Serializable'.
4 class Product implements Serializable {
~~~~~~~
Why the compiler said that
implements is a promise the class keeps every member of the interface. Serializable requires a serialize() method, and Product never defines one, so the compiler refuses to accept that the promise is kept.
The fix
Add the missing method with a compatible signature.
SeniorA class implements two interfaces that each declare a method with the same name but incompatible signatures. What happens?
The class only compiles if a single method implementation can structurally satisfy both interfaces at once — sometimes possible by widening a parameter to a union or a return type to an intersection. If no single signature can satisfy both, the class cannot implement both interfaces as written; one of them has to change, or the class has to pick one and adapt to the other through a separate wrapper.
What they are really testing: Understanding structural typing at the boundary of multiple interface implementations, an edge case most junior developers never hit.
Quick check
What is the key difference between an interface and an abstract class in TypeScript?
An interface is a pure shape with no implementation at all. An abstract class can mix abstract members with real, concrete method bodies that subclasses inherit unchanged.
06
Static members, getters and setters
✓
A static member belongs to the class itself, not to any one instance — there is exactly one copy, shared across every object created from that class. get and set accessors let a piece of code that reads or writes like a plain field actually run a method behind the scenes, useful for computed values or validation on assignment.
typescriptEdit it. ⌘/Ctrl + Enter runs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class IdGenerator {privatestatic nextId =1readonly id: number
constructor(){this.id = IdGenerator.nextId
IdGenerator.nextId +=1}staticreset():void{
IdGenerator.nextId =1}}const first =new IdGenerator()const second =new IdGenerator()console.log(first.id, second.id)
IdGenerator.reset()const third =new IdGenerator()console.log(third.id)
You should see
1 2
1
Your turn
Add a static method IdGenerator.count(): number that returns how many IDs have been handed out so far (without resetting anything), and log it.
typescriptEdit it. ⌘/Ctrl + Enter runs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Temperature {private _celsius: number =0getcelsius(): number {returnthis._celsius
}setcelsius(value: number){this._celsius = value
}getfahrenheit(): number {returnthis._celsius *9/5+32}}const t =new Temperature()
t.celsius =25console.log(t.celsius, t.fahrenheit)
You should see
25 77
Your turn
Add a setter for fahrenheit that converts back and assigns this._celsius, so t.fahrenheit = 32 makes t.celsius read 0.
Accessors read and write like fields, but run code
From the outside, t.celsius = 25 and t.celsius look exactly like touching a plain field — the caller never writes (). Internally, TypeScript compiles get/set pairs to Object.defineProperty, so a setter can validate or transform a value on the way in, and a getter can compute one on the way out, invisibly to the caller.
Static member
A field or method that belongs to the class itself, one copy shared by every instance, accessed as ClassName.member.
Getter
A get method called with property syntax (no parentheses) that computes or returns a value.
Setter
A set method called with assignment syntax (property = value) that runs code instead of a plain assignment.