get) and Setters (set) allow you to intercept property reads and writes in JavaScript classes. TypeScript enhances accessors by automatically enforcing readonly constraints on getter-only properties and supporting Asymmetric Accessor Types.1. Accessor Syntax & Encapsulation#
Accessors look like methods in their declaration syntax, but they are consumed like standard object properties without appending function invocation parentheses ().
class BankAccount {
// Private backing field (by convention prefixed with '_')
private _balance: number = 0;
constructor(initialBalance: number) {
this.balance = initialBalance; // Routes through setter validation!
}
// Getter: Invoked when reading account.balance
get balance(): number {
return this._balance;
}
// Setter: Invoked when writing account.balance = 500
set balance(newBalance: number) {
if (newBalance < 0) {
throw new Error("[Validation Error]: Account balance cannot be negative.");
}
this._balance = newBalance;
}
}
const account = new BankAccount(100);
// Reading via Getter (No parentheses!)
console.log(account.balance); // 100
// Writing via Setter
account.balance = 250;
console.log(account.balance); // 250
// ❌ Throws Runtime Exception: [Validation Error]: Account balance cannot be negative.
// account.balance = -50;
2. Computed Read-Only Properties (Getter Only)#
If a class defines a get accessor without a corresponding set accessor, TypeScript automatically infers the property as readonly. Any attempt to assign a value to a getter-only property will result in a compile-time type error.
class Circle {
constructor(public radius: number) {}
// Computed property getter (No setter provided)
get area(): number {
return Math.PI * (this.radius ** 2);
}
get circumference(): number {
return 2 * Math.PI * this.radius;
}
}
const circle = new Circle(5);
console.log(circle.area); // ~78.54
// ❌ Compiler Error: Cannot assign to 'area' because it is a read-only property.
// circle.area = 100;
3. Backing Field Patterns: _field vs #field#
Because an accessor cannot share the exact same identifier as its backing storage property (e.g. get name() cannot read this.name, as that triggers infinite recursion), you must store the raw data in a distinct field.
Pattern A: Conventional Prefix (_field)#
class UserA {
private _email: string = "";
get email(): string { return this._email; }
set email(val: string) { this._email = val; }
}Pattern B: Native ES2022 Private Backing Field (#field)#
class UserB {
// Uses native hard privacy for backing storage
#email: string = "";
get email(): string {
return this.#email;
}
set email(val: string) {
if (!val.includes("@")) throw new Error("Invalid email format");
this.#email = val;
}
}4. Asymmetric Accessor Types (TypeScript 4.3+)#
Prior to TypeScript 4.3, a setter was required to have the exact same type as its getter.
Since TypeScript 4.3, you can declare Asymmetric Accessor Types, where the setter accepts a broader type than the getter returns. This is ideal for parsing string inputs or timestamp numbers into structured domain objects!
class ScheduleEvent {
private _eventDate: Date = new Date();
// 1. Getter returns a strict, guaranteed Date object
get eventDate(): Date {
return this._eventDate;
}
// 2. Setter accepts Date, ISO string, or Unix epoch timestamp number!
set eventDate(value: Date | string | number) {
if (value instanceof Date) {
this._eventDate = value;
} else if (typeof value === "string") {
this._eventDate = new Date(value);
} else if (typeof value === "number") {
this._eventDate = new Date(value);
} else {
throw new Error("Invalid date input type.");
}
}
}
const event = new ScheduleEvent();
// Assigning a string to the setter:
event.eventDate = "2026-12-31T23:59:59Z";
// Assigning a Unix timestamp number to the setter:
event.eventDate = 1700000000000;
// The getter strictly returns a Date instance with date methods:
const year: number = event.eventDate.getFullYear();
console.log(`Event Year: ${year}`);Type Signature of Asymmetric Accessors#
When inspecting eventDate on ScheduleEvent:
- Read Type:
Date - Write Type:
Date | string | number
This provides maximum flexibility for consumers writing data, while guaranteeing strict, predictable types when reading data!
5. Compiler Target Requirements#
To compile getters and setters, your tsconfig.json target must be set to ES5 or higher:
{
"compilerOptions": {
"target": "ES2022" // Required for Object.defineProperty / accessor emission
}
}If target is set to ES3, tsc will throw an error because ES3 JavaScript engines did not support property accessors.
Summary & Next Steps#
In this episode:
- We implemented class encapsulation using
getandsetaccessors. - We proved that getter-only properties are automatically inferred as
readonly. - We compared
_fieldsoft private vs#fieldhard private backing storage. - We built flexible input parsers using Asymmetric Accessor Types (
get Date/set Date | string | number).
In Episode 20: Implementing Interfaces (implements), we will explore how classes enforce architectural contracts using interface implementation!

