static
Attaches a property, method or initializer block to the class itself rather than to its instances.
class C {
static property: Type = value;
static readonly CONSTANT = value;
private static hidden: Type;
static #trulyPrivate: Type;
static method(...args): R { }
static get computed(): Type { }
static { /* static initialization block */ }
}- Added in TypeScript 1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$property | any | A value stored on the constructor object itself. One copy exists no matter how many instances are created. |
$method | function | Called as C.method(...). Inside it, `this` is the constructor object, so calling it detached loses the receiver. |
$static { }optional | initialization block | A statement block with its own scope that runs once when the class is defined, with access to the class's private members. Multiple blocks run in source order. |
Errors
Referencing a class type parameter from a static member is an error: "Static members cannot reference class type parameters." Naming a static member `name`, `length` or `caller` collides with existing properties of Function and is also an error.
Changelog
- PHP 4.3Static members can be given # private names, making them hard-private at runtime.
- PHP 4.4Static initialization blocks added. A class may contain several, and they run in the order written.
A static member belongs to the constructor object, not to instances. There is exactly one of it, it is reachable as ClassName.member, and it is inherited: a subclass constructor sees the base class's statics through the prototype chain.
When to reach for it
- Named alternative constructors —
Money.fromCents(500)reads better than a constructor with a mode flag. - Constants and lookup tables that belong to the concept the class models.
- Registries, caches and instance counters shared by every instance.
- A static block when initialization needs real statements: a try/catch, a loop, or setup that touches the class's own private members.
What trips people up
Statics cannot reference the class's type parameters. class Box<T> { static empty: T } is an error, and it has to be: there is one static slot shared by Box<string> and Box<number> alike, so no single T exists. Make the static member generic itself instead. Second, inheritance of statics is real inheritance, not copying — a subclass that mutates an inherited static array mutates the base class's array, because both names resolve to the same object. Third, this inside a static method is the constructor, so passing Registry.register as a callback drops the receiver.
Example
When to use it
- Provide named factory methods such as `Money.fromCents()` alongside the real constructor.
- Keep a registry or instance counter shared by every instance of a class.
- Run try/catch initialization for a class constant in a static block, with access to the class's private fields.
Frequently asked questions
Why can't static members use class type parameters in TypeScript?
Because there is only one copy of a static member for the whole class, shared by every instantiation — Box<string> and Box<number> use the same constructor object, so a static typed T would need to be two types at once. Give the static method its own type parameter instead.
Are static members inherited by subclasses in TypeScript?
Yes, through the constructor's prototype chain, so a subclass can read and call them without redeclaring anything. That also means an inherited object or array is the same object — mutating it from the subclass mutates the base class's copy until the subclass declares its own field.
What is a static block used for?
Running initialization that needs real statements rather than a single expression: try/catch around setup, a loop, or code that touches the class's own private static fields. Blocks run once when the class is defined, in the order they appear in the class body.
More examples
The type-parameter restriction, and the way around it
Prints "hello 42" and "true true". The commented-out field is the error people hit first: Box<string> and Box<number> share one constructor object, so a single static slot could not have both types. Moving the type parameter onto the static method itself gives each call its own U and the factory infers it from the argument.
class Box<T> {
constructor(public value: T) {}
// static empty: T | undefined;
// Error: Static members cannot reference class type parameters.
// Fine: the method carries its own type parameter.
static of<U>(value: U): Box<U> {
return new Box(value);
}
}
const a = Box.of("hello");
const b = Box.of(42);
console.log(a.value, b.value);
console.log(a instanceof Box, b instanceof Box);Inherited statics are shared, not copied
Prints ["toolbar"] for both Plugin and UiPlugin, and ["csv"] for DataPlugin. UiPlugin never declared a registry of its own, so the name resolves up the prototype chain to Plugin's single array and the two classes write to the same list. DataPlugin redeclares the field, giving it a separate slot.
class Plugin {
static registry: string[] = [];
static register(name: string): void {
this.registry.push(name);
}
}
class UiPlugin extends Plugin {}
class DataPlugin extends Plugin {
// Shadowing with its own slot is the fix.
static override registry: string[] = [];
}
UiPlugin.register("toolbar");
DataPlugin.register("csv");
console.log("Plugin :", Plugin.registry);
console.log("UiPlugin:", UiPlugin.registry);
console.log("Data :", DataPlugin.registry);
Discussion