A TypeScript class is a JavaScript class with typed fields and access rules. public members are reachable anywhere, protected ones inside the class and its subclasses, private ones only inside the class. Those three checks exist only at compile time. A #name field is private in the running JavaScript too, and readonly blocks writes after the constructor.
| Keyword | Effect | Checked when |
|---|---|---|
| public | Reachable from anywhere (the default) | Compile time |
| protected | Class and subclasses only | Compile time |
| private | Declaring class only | Compile time |
| #field | Declaring class only, enforced by JavaScript | Compile time and runtime |
| readonly | Assign only in the declaration or constructor | Compile time |
| abstract | No instances; subclasses must fill in members | Compile time |
| implements | Checks the class matches an interface | Compile time |
| override | Marks a method that replaces a base method | Compile time |
Updated
Choose a modifier and the matrix shows five ways code might reach that member, with the tsc result for each. The bracket row is the one that surprises people: the compile-time modifiers can be sidestepped with a string key, while a # field cannot.
class Account {
public owner = "Ada";
protected tier = "pro";
private balance = 100;
#pin = 1234;
readonly id: number = 7;
}
class Savings extends Account { /* subclass */ }
const acct = new Account();No restrictions. Leaving the modifier off means the same thing.
| Used from | Code | tsc result |
|---|---|---|
| Inside Account's own methods | this.owner | Compiles |
| Inside a Savings method | this.owner | Compiles |
| Outside, with a dot | acct.owner | Compiles |
| Outside, with a string key | acct["owner"] | Compiles |
| Outside, assigning a new value | acct.owner = "Bo" | Compiles |
The constructor declares two fields in its parameter list. The cache is a # field, so nothing outside the class can read or clear it.
class PriceService {
#cache = new Map<string, number>();
constructor(private readonly baseUrl: string, public currency = "USD") {}
async price(sku: string): Promise<number> {
const hit = this.#cache.get(sku);
if (hit !== undefined) return hit;
const res = await fetch(`${this.baseUrl}/prices/${sku}`);
const { amount }: { amount: number } = await res.json();
this.#cache.set(sku, amount);
return amount;
}
}describe is shared code; area is the hook each shape must supply. With noImplicitOverride on, the override keyword is required on area in Circle.
abstract class Shape {
abstract area(): number;
describe(): string {
return `Area: ${this.area().toFixed(2)}`;
}
}
class Circle extends Shape {
constructor(public radius: number) { super(); }
override area(): number {
return Math.PI * this.radius ** 2;
}
}
new Circle(2).describe(); // "Area: 12.57"The interface makes sure the class has send with the right shape, but the parameter still needs its own type annotation.
interface Notifier {
send(message: string): Promise<void>;
}
class ConsoleNotifier implements Notifier {
async send(message: string): Promise<void> {
console.log(message);
}
}
A field declaration such as count = 0 or name: string tells the compiler what the instance holds. Under strict mode each field needs a value by the end of the constructor, which catches the classic bug of a field that is undefined on first use.
Parameter properties fold the declare-then-assign dance into one line. They are handy for services that receive their dependencies through the constructor, though some teams avoid them because they produce real assignment code rather than pure type syntax.
public, protected and private are erased when TypeScript emits JavaScript. They stop teammates from misusing a member in typed code, but anything running the output, including a console session, can still read the property.
JavaScript private fields with a # prefix are part of the language. The engine refuses access from outside the class body, they do not show up in Object.keys, and subclasses cannot see them either.
An interface describes a shape; a class produces objects that have it. A class can implement several interfaces, and any object with the right members satisfies the interface whether or not a class was involved, because TypeScript compares shapes rather than names.
The interfaces guide on this site covers declaring and extending those shapes. This page is about the class side: construction, access and inheritance.
private is a TypeScript check. It disappears from the output, and obj["secret"] still compiles and reads the value. A #secret field is a JavaScript private field: there is no string key that reaches it, and code outside the class cannot read it at runtime.
A shortcut that declares and assigns a field from a constructor parameter. Writing constructor(private readonly id: string) {} creates a private readonly id field with no body needed. The prefix can be public, protected, private or readonly.
No. implements only checks that the finished class fits the interface. Method parameters still need their own annotations; leaving one out under strict mode is an implicit any error, even though the interface already names its type.
strictPropertyInitialization, which strict turns on, wants every declared field set in the declaration or constructor. Give it a value, make it optional, or add ! after the name if something else sets it before use.
Use an abstract class when subclasses should share real code, such as a method that calls an abstract hook. An interface carries no code and a class can implement several, so it suits describing shapes.
Related: TypeScript interfaces, TypeScript data types, TypeScript vs JavaScript, TypeScript enums and alternatives, React with TypeScript.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.