| |

TypeScript 24 ๐Ÿ”ท Static Members and Class Expressions

Classes have two sides: the instance side โ€” properties and methods that live on each object created with new โ€” and the static side โ€” properties and methods that live on the class itself. Static members belong to the class, not to any instance. They’re used for factory methods, shared constants, counters, caches, and utility functions that logically belong to the class but don’t depend on a specific instance. Class expressions are the other half of this chapter โ€” a class defined as an expression rather than a declaration, which can be assigned to a variable, passed around, and used anonymously. Together they round out how classes work in TypeScript.

Key point: A static member lives on the class, not on instances โ€” ClassName.member, not instance.member. A class expression is a class value that can be assigned, passed, or returned โ€” useful for factories, decorators, and functional patterns. Static members are shared state or behavior; class expressions are classes as first-class values. Both extend what “class” means beyond the simple declaration.


What a static member is

A static member is declared with the static keyword. It exists on the class itself, not on instances.

class Counter {
  static count = 0;

  static increment(): void {
    Counter.count++;
  }
}

Counter.count;         // 0
Counter.increment();
Counter.count;         // 1

count and increment live on the Counter class. There’s no instance involved. new Counter() isn’t needed.

Static vs instance:

MemberAccess
Instanceinstance.member
StaticClassName.member
class User {
  name = '';                    // instance property
  static maxAge = 150;          // static property

  greet(): string {             // instance method
    return `Hi, ${this.name}`;
  }

  static create(name: string): User {  // static method
    const u = new User();
    u.name = name;
    return u;
  }
}

const u = new User();
u.name;              // โœ… instance
u.maxAge;            // โŒ not on instance
User.maxAge;         // โœ… static
User.create('Alice'); // โœ… static method

What static members are for:

  • Factory methods โ€” User.create(), Config.from()
  • Constants โ€” Math.PI, Number.MAX_VALUE
  • Counters โ€” how many instances exist
  • Caches โ€” shared across instances
  • Utility methods โ€” belong to the class conceptually, not any instance
  • Singletons โ€” the singleton instance lives as a static field

What static members are not:

  • Accessible via instances (instance.staticMember is undefined)
  • Tied to any particular object
  • Inherited the same way (they’re inherited, but see below)

Why static members exist: Some things belong to a class conceptually but not to any instance. Math.PI doesn’t depend on a Math object. User.create() creates a user but isn’t one. Static members express that โ€” they’re on the class, usable without an instance, and shared across all instances.


Static properties

A static property is a single value shared by the class.

class Config {
  static readonly VERSION = '1.0.0';
  static readonly MAX_CONNECTIONS = 100;
  static readonly API_URL = 'https://api.example.com';
}

Config.VERSION;         // '1.0.0'
Config.MAX_CONNECTIONS; // 100

Constants are the most common static property. static readonly makes them immutable.

Mutable static state:

class Counter {
  static count = 0;

  constructor() {
    Counter.count++;
  }
}

new Counter();
new Counter();
Counter.count;   // 2

Counter.count tracks how many instances were created. Every new Counter() increments it.

Shared cache:

class UserCache {
  private static cache = new Map<number, User>();

  static get(id: number): User | undefined {
    return this.cache.get(id);
  }

  static set(id: number, user: User): void {
    this.cache.set(id, user);
  }
}

The cache is shared across all usages of UserCache. No instance needed.

Lazy static initialization:

class App {
  private static instance: App | null = null;

  static get(): App {
    return App.instance ??= new App();
  }

  private constructor() {}
}

App.instance is lazily created on first App.get(). The private constructor prevents external new.

Static blocks: For complex initialization, a static { } block runs once when the class is loaded.

class Settings {
  static defaults: Record<string, string>;

  static {
    const env = process.env.NODE_ENV ?? 'dev';
    Settings.defaults = env === 'prod'
      ? { api: 'https://api.example.com' }
      : { api: 'http://localhost:3000' };
  }
}

Settings.defaults.api;   // depends on NODE_ENV

The static block runs once, when the class definition is evaluated. Useful for setup that needs logic.

Why static blocks: Some initialization needs more than a single expression โ€” reading env vars, parsing config, computing values. A static block runs that logic once, at class load. It’s the class-level equivalent of a constructor.

Why static state matters: Some state belongs to the class, not instances. A cache, a counter, a singleton โ€” these are shared across everything that uses the class. Static properties make that explicit. Use them carefully: shared mutable state is a source of bugs if not managed.


Static methods

A static method is called on the class, not on an instance.

class MathUtils {
  static square(n: number): number {
    return n * n;
  }

  static clamp(n: number, min: number, max: number): number {
    return Math.max(min, Math.min(max, n));
  }
}

MathUtils.square(5);          // 25
MathUtils.clamp(15, 0, 10);   // 10

Factory methods are the classic static method pattern.

class User {
  constructor(
    public readonly id: string,
    public name: string,
    public email: string
  ) {}

  static create(name: string, email: string): User {
    return new User(crypto.randomUUID(), name, email);
  }

  static fromJson(json: string): User {
    const data = JSON.parse(json);
    return new User(data.id, data.name, data.email);
  }
}

User.create('Alice', 'alice@example.com');
User.fromJson('{"id":"1","name":"Alice","email":"a@b.c"}');

Factories centralize construction. They can validate, generate defaults, parse input, or return cached instances.

this in static methods: Inside a static method, this refers to the class โ€” not an instance.

class Base {
  static name = 'base';

  static getName(): string {
    return this.name;   // 'base', or subclass's name if called via subclass
  }
}

class Sub extends Base {
  static name = 'sub';   // โš ๏ธ shadows Base.name
}

Base.getName();   // 'base'
Sub.getName();    // 'sub'

this in a static method is dynamic โ€” it’s whatever class the method was called on. That lets static methods be inherited and used with subclass context.

Caution: A subclass’s static property with the same name shadows the parent’s. Sub.name replaces Base.name for Sub.getName().

Static methods can’t access instance members:

class User {
  name = 'Alice';

  static greet(): string {
    return `Hi, ${this.name}`;   // โŒ this.name is the class's name, not an instance's
  }
}

Static methods don’t have access to instance state. They can create instances and use them, but they can’t reach into this.name expecting instance data.

Utility class pattern:

class Strings {
  private constructor() {}   // prevent instantiation

  static capitalize(s: string): string {
    return s.charAt(0).toUpperCase() + s.slice(1);
  }

  static reverse(s: string): string {
    return [...s].reverse().join('');
  }
}

Strings.capitalize('hello');   // 'Hello'
new Strings();                 // โŒ private constructor

A class with only static methods and a private constructor is a namespace โ€” a way to group related functions. TypeScript has namespace for this too, but a class with statics is simpler.

Why static factory methods: They let you control how instances are created. User.create() can generate an ID, validate input, or return a cached object. A plain new User() doesn’t have that flexibility. Factories also let you change the constructor later without breaking callers who use the factory.


Static inheritance

Static members are inherited by subclasses, but with a twist โ€” this in a static method refers to the class the method was called on.

class Animal {
  static species = 'unknown';

  static describe(): string {
    return `Species: ${this.species}`;
  }
}

class Dog extends Animal {
  static species = 'canine';
}

Animal.describe();   // 'Species: unknown'
Dog.describe();      // 'Species: canine' โ€” `this` is Dog

Dog inherits describe. When called as Dog.describe(), this is Dog, so this.species is 'canine'. That’s the point โ€” static methods can be reused with subclass context.

Overriding static methods:

class Base {
  static create(): Base {
    return new Base();
  }
}

class Sub extends Base {
  static override create(): Sub {
    return new Sub();
  }
}

Sub.create() returns a Sub, not a Base. The override keyword works for static methods too, catching typos.

Static this type:

class Base {
  static create<T extends typeof Base>(this: T): InstanceType<T> {
    return new this() as InstanceType<T>;
  }
}

class Sub extends Base {
  name = 'sub';
}

const s = Sub.create();
s.name;   // 'sub'

The this: T parameter types this as the class being called. InstanceType<T> gives the instance type. This makes Sub.create() return a Sub. Advanced but useful for factory methods in class hierarchies.

Why static inheritance matters: It lets you write generic factory or utility methods on a base class and have them work correctly for subclasses. The this context makes each call resolve to the right class.

Why this is dynamic in statics: Static methods are shared code. If describe always used Animal.species, subclasses couldn’t customize it. By using this.species, the method reads from whichever class it was called on. That’s polymorphism for the static side.


Class expressions

A class expression is a class defined in an expression position โ€” assigned to a variable, passed to a function, returned from another function.

const Point = class {
  constructor(public x: number, public y: number) {}
};

Point is a variable holding a class. new Point(1, 2) works exactly like a class declaration.

Named class expressions:

const Point = class PointClass {
  constructor(public x: number, public y: number) {}

  describe(): string {
    return `(${this.x}, ${this.y})`;
  }
};

const p = new Point(1, 2);
p.describe();   // '(1, 2)'

The inner name (PointClass) is only visible inside the class body โ€” useful for recursion or self-reference.

Anonymous class expressions:

const createLogger = () => class {
  log(msg: string): void {
    console.log(msg);
  }
};

const Logger = createLogger();
const l = new Logger();
l.log('hello');

The class has no name. That’s fine when the class is only used via the variable.

Passing classes as values:

type Constructor<T> = new (...args: unknown[]) => T;

function instantiate<T>(Ctor: Constructor<T>): T {
  return new Ctor();
}

const C = class { name = 'anonymous' };
const instance = instantiate(C);

A class expression can be passed as a value. The Constructor<T> type describes “something newable returning T.”

Returning classes from functions:

function createModel<T>(defaults: T) {
  return class {
    data: T = { ...defaults };

    reset(): void {
      this.data = { ...defaults };
    }
  };
}

const UserModel = createModel({ name: '', email: '' });
const u = new UserModel();
u.data.name = 'Alice';
u.reset();
u.data.name;   // ''

The factory returns a class tailored to the defaults. Each call creates a new class. This is a form of metaprogramming โ€” generating classes on demand.

When to use class expressions:

  • Factories that produce classes
  • Mixins โ€” functions that combine classes
  • Decorators that replace classes
  • Quick, one-off classes assigned to a variable
  • Classes with captured state from a closure

Why class expressions exist: Declarations are statements โ€” they don’t return a value. Expressions do. When you need a class as a value โ€” to pass, return, or compute โ€” a class expression is the way. It’s the same class syntax, just in expression position, and it enables factories, mixins, and decorators.


Static typing and typeof

The type of a class is typeof ClassName โ€” the constructor type.

class User {
  constructor(public name: string) {}
  greet(): string { return `Hi, ${this.name}`; }
}

type UserConstructor = typeof User;
// { new (name: string): User; prototype: User }

typeof User describes the constructor function โ€” including new, static members, and the prototype.

InstanceType<T>: Extracts the instance type from a constructor type.

type UserInstance = InstanceType<typeof User>;
// User

Using typeof in functions:

function create<T>(Ctor: new (...args: never[]) => T): T {
  return new Ctor();
}

const u = create(User);   // u: User

Ctor is a constructor type. The function creates an instance without knowing the specific class.

Static members in typeof:

class Config {
  static readonly VERSION = '1.0.0';
  static load(): void {}
}

type ConfigCtor = typeof Config;
// includes: new (), VERSION, load

const c: ConfigCtor = Config;
c.VERSION;    // '1.0.0'
c.load();

typeof Config includes the static members. A variable typed as typeof Config can access them.

Why typeof matters: It’s how you refer to the class itself as a type โ€” as opposed to instances of the class. User is the instance type; typeof User is the constructor type. Knowing the difference lets you write generic factories, decorators, and mixins.

Why two types per class: A class produces two things โ€” the constructor value and the instance type. typeof User is the constructor’s type; User is the instance’s type. TypeScript keeps them separate so you can type variables holding either.


Mixins via class expressions

A mixin is a function that takes a class and returns a new class with added behavior. Class expressions make this possible.

type Constructor<T = {}> = new (...args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    createdAt = new Date();
    updatedAt = new Date();

    touch(): void {
      this.updatedAt = new Date();
    }
  };
}

function Identified<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    readonly id = crypto.randomUUID();
  };
}

class Entity {
  name = '';
}

const User = Identified(Timestamped(Entity));

const u = new User();
u.name = 'Alice';
u.id;            // has id
u.createdAt;     // has createdAt
u.touch();       // has touch

Each mixin wraps the base class and adds members. Applying both gives a class with id, createdAt, updatedAt, and touch โ€” combined without inheritance.

Why mixins: TypeScript only supports single inheritance. Mixins let you compose behavior from multiple sources. Each mixin is a function that returns an enriched class. Combined, they build up a class with many capabilities.

Type inference: The returned class’s type is inferred from the base and the added members. TypeScript tracks what each mixin contributes, so the final class has all the members.

The limitation: TypeScript can’t always infer deeply nested mixin types perfectly, especially with generics. Sometimes you need explicit type annotations to keep the compiler happy.

Why mixins matter: They’re the standard pattern for combining behaviors without multiple inheritance. Need a class that’s Serializable, Comparable, and Timestamped? Mixins let you compose those traits. The alternative โ€” a deep hierarchy โ€” wouldn’t work because inheritance is single.


A full example

A class hierarchy with statics, factories, and a mixin.

// ============================================
// MIXIN
// ============================================

type Constructor<T = {}> = new (...args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    readonly createdAt = new Date();
    updatedAt = new Date();

    touch(): void {
      this.updatedAt = new Date();
    }
  };
}

// ============================================
// BASE CLASS WITH STATICS
// ============================================

class Entity {
  static count = 0;

  readonly id: string;
  name: string;

  constructor(name: string) {
    this.id = crypto.randomUUID();
    this.name = name;
    Entity.count++;
  }

  static reset(): void {
    Entity.count = 0;
  }

  static create<T extends typeof Entity>(
    this: T,
    name: string
  ): InstanceType<T> {
    return new this(name) as InstanceType<T>;
  }

  describe(): string {
    return `${this.name} (${this.id.slice(0, 8)})`;
  }
}

// ============================================
// SUBCLASSES
// ============================================

class User extends Entity {
  constructor(name: string, public email: string) {
    super(name);
  }

  override describe(): string {
    return `User: ${super.describe()}`;
  }
}

class Product extends Entity {
  constructor(name: string, public price: number) {
    super(name);
  }

  override describe(): string {
    return `Product: ${super.describe()} โ€” $${this.price}`;
  }
}

// ============================================
// WITH MIXIN
// ============================================

const TimestampedUser = Timestamped(User);

// ============================================
// CLASS EXPRESSION
// ============================================

const Admin = class extends User {
  constructor(name: string, email: string, public permissions: string[]) {
    super(name, email);
  }

  override describe(): string {
    return `Admin: ${this.name} [${this.permissions.join(', ')}]`;
  }
};

// ============================================
// USAGE
// ============================================

const u = new User('Alice', 'alice@example.com');
const p = new Product('Keyboard', 79.99);
const a = new Admin('Bob', 'bob@example.com', ['read', 'write']);

console.log(u.describe());
console.log(p.describe());
console.log(a.describe());
console.log(`Total entities: ${Entity.count}`);

// Factory
const created = User.create('Carol', 'carol@example.com');
console.log(created.describe());

// Mixin instance
const tu = new TimestampedUser('Dave', 'dave@example.com');
console.log(tu.describe());
console.log(tu.createdAt);
tu.touch();

// Reset counter
Entity.reset();
console.log(`After reset: ${Entity.count}`);

What this shows:

  • Entity โ€” base class with static count, static create, and a static method using this: T for subclass-aware factories
  • User, Product โ€” subclasses with overrides
  • Admin โ€” a class expression extending User
  • Timestamped โ€” mixin adding createdAt and touch
  • Static counter incremented in the constructor

Why this shape: It’s a realistic combination โ€” statics for shared state and factories, a hierarchy for is-a relationships, a mixin for cross-cutting concerns, and a class expression for on-the-fly extension. All four features in one example.


Complete Example Session

# ============================================
# PART 1: STATIC MEMBERS
# ============================================

cat > statics.ts << 'EOF'
class Counter {
  static count = 0;

  constructor() {
    Counter.count++;
  }

  static reset(): void {
    Counter.count = 0;
  }
}

new Counter();
new Counter();
new Counter();
console.log(Counter.count);   // 3
Counter.reset();
console.log(Counter.count);   // 0
EOF

npx tsc --noEmit statics.ts
# (no errors)

# ============================================
# PART 2: STATIC CONSTANTS
# ============================================

cat > consts.ts << 'EOF'
class Config {
  static readonly VERSION = '1.0.0';
  static readonly MAX = 100;
}

console.log(Config.VERSION, Config.MAX);
// Config.VERSION = '2.0';  // โŒ readonly
EOF

npx tsc --noEmit consts.ts
# (no errors)

# ============================================
# PART 3: STATIC FACTORY
# ============================================

cat > factory.ts << 'EOF'
class User {
  constructor(
    public readonly id: string,
    public name: string,
    public email: string
  ) {}

  static create(name: string, email: string): User {
    return new User(crypto.randomUUID(), name, email);
  }

  static fromJson(json: string): User {
    const d = JSON.parse(json);
    return new User(d.id, d.name, d.email);
  }
}

const u = User.create('Alice', 'alice@example.com');
console.log(u.name, u.id.slice(0, 8));
EOF

npx tsc --noEmit factory.ts
# (no errors)

# ============================================
# PART 4: STATIC INHERITANCE
# ============================================

cat > inherit.ts << 'EOF'
class Animal {
  static species = 'unknown';

  static describe(): string {
    return `Species: ${this.species}`;
  }
}

class Dog extends Animal {
  static species = 'canine';
}

console.log(Animal.describe());
console.log(Dog.describe());
EOF

npx tsc --noEmit inherit.ts
# (no errors)

# ============================================
# PART 5: STATIC BLOCK
# ============================================

cat > block.ts << 'EOF'
class Settings {
  static defaults: Record<string, string>;

  static {
    const env = process.env.NODE_ENV ?? 'dev';
    Settings.defaults = env === 'prod'
      ? { api: 'https://api.example.com' }
      : { api: 'http://localhost:3000' };
  }
}

console.log(Settings.defaults.api);
EOF

npx tsc --noEmit block.ts
# (no errors)

# ============================================
# PART 6: CLASS EXPRESSION
# ============================================

cat > expr.ts << 'EOF'
const Point = class {
  constructor(public x: number, public y: number) {}

  describe(): string {
    return `(${this.x}, ${this.y})`;
  }
};

const p = new Point(3, 4);
console.log(p.describe());

// Class as value
type Ctor<T> = new (...args: any[]) => T;
function instantiate<T>(C: Ctor<T>): T { return new C(); }

const Anonymous = class { value = 42 };
console.log(instantiate(Anonymous).value);
EOF

npx tsc --noEmit expr.ts
# (no errors)

# ============================================
# PART 7: MIXIN
# ============================================

cat > mixin.ts << 'EOF'
type Ctor<T = {}> = new (...args: any[]) => T;

function Timestamped<TBase extends Ctor>(Base: TBase) {
  return class extends Base {
    readonly createdAt = new Date();
    updatedAt = new Date();

    touch(): void {
      this.updatedAt = new Date();
    }
  };
}

class Entity {
  name = '';
}

const User = Timestamped(Entity);
const u = new User();
u.name = 'Alice';
console.log(u.name);
console.log(u.createdAt instanceof Date);
u.touch();
EOF

npx tsc --noEmit mixin.ts
# (no errors)

# ============================================
# PART 8: COMPILE AND RUN
# ============================================

npx tsc statics.ts consts.ts factory.ts inherit.ts block.ts expr.ts mixin.ts
node statics.js
# [ 3 ]
# [ 0 ]

node consts.js
# [ 1.0.0 100 ]

node factory.js
# [ Alice <8-char-uuid> ]

node inherit.js
# [ Species: unknown ]
# [ Species: canine ]

node block.js
# [ http://localhost:3000 ]

node expr.js
# [ (3, 4) ]
# [ 42 ]

node mixin.js
# [ Alice ]
# [ true ]

Quick Reference

Static Syntax

FormExample
Propertystatic count = 0
Readonlystatic readonly MAX = 100
Methodstatic create(): User { }
Privatestatic #cache = new Map()
Blockstatic { /* init */ }

Access

MemberAccess
Instancenew C().method()
StaticC.method()
Instance member on classโŒ
Static member on instanceโŒ (undefined)

Static Members

TypeUse for
PropertyConstants, counters, caches
MethodFactories, utilities
BlockComplex initialization
Private staticHidden shared state

Factory Patterns

PatternExample
Createstatic create(...): T
From JSONstatic fromJson(s): T
Singletonstatic get(): T
Builderstatic builder(): Builder

Static Inheritance

AspectBehavior
Inheritedโœ…
this in staticRefers to called class
Subclass overrideโœ…
Shadow static propertyReplaces parent’s

Class Expressions

FormExample
Anonymousconst C = class { }
Namedconst C = class Named { }
Returnedreturn class { }
Passedfn(class { })

typeof and Instances

ExpressionMeaning
UserInstance type
typeof UserConstructor type
InstanceType<typeof User>Instance type
new User()Instance value

Mixin Pattern

StepCode
Constructor typetype Ctor<T = {}> = new (...args: any[]) => T
Mixin functionfunction M<TBase extends Ctor>(Base: TBase) { }
Returnsreturn class extends Base { }
Applyconst Mixed = M(Base)

When to Use Static

Use caseStatic
Factoryโœ…
Constantโœ…
Counterโœ…
Cacheโœ…
Utilityโœ…
Shared stateโœ…
Per-instance dataโŒ
Polymorphic behaviorโŒ (usually)

When to Use Class Expressions

Use caseExpression
Factory returning classโœ…
Mixinโœ…
Decoratorโœ…
One-off classโœ…
Simple declarationโŒ
Named top-level classโŒ

Errors

ErrorCause
Property does not exist on type 'typeof C'Instance member via class
Property does not exist on type 'C'Static member via instance
Class expression is not callableMissing new
not assignable to typeStatic this context mismatch

Best Practices

โœ… Do This:

// Use static for constants
class Config {
  static readonly VERSION = '1.0.0';
}                                                          // โœ…

// Use static factories
static create(name: string): User {
  return new User(crypto.randomUUID(), name);
}                                                          // โœ…

// Use static blocks for complex init
static {
  Settings.defaults = loadDefaults();
}                                                          // โœ…

// Prevent instantiation of utility classes
class Utils {
  private constructor() {}
  static doThing(): void {}
}                                                          // โœ…

// Use `this: T` in generic factories
static create<T extends typeof Entity>(
  this: T, name: string
): InstanceType<T> {
  return new this(name) as InstanceType<T>;
}                                                          // โœ…

// Use class expressions for factories
const createModel = <T>(defaults: T) => class {
  data = { ...defaults };
};                                                         // โœ…

// Use mixins for cross-cutting concerns
const Mixed = Timestamped(Identified(Entity));             // โœ…

// Mark overridden statics with override
static override create(): Sub { return new Sub(); }        // โœ…

โŒ Don’t Do This:

// Don't use static for per-instance data
class User {
  static name = '';  // โš ๏ธ  shared across all "instances"          // โš ๏ธ
}

// Don't use static methods that need instance state
static greet(): string {
  return `Hi, ${this.name}`;  // โŒ this is the class             // โŒ
}

// Don't shadow static properties unknowingly
class Base { static name = 'base'; }
class Sub extends Base { static name = 'sub'; }  // โš ๏ธ  shadow     // โš ๏ธ

// Don't expect static members on instances
instance.staticMethod();  // โŒ undefined                          // โŒ

// Don't forget `new` with class expressions
const C = class {};
C();  // โŒ must use new                                           // โŒ

// Don't abuse mixins for simple reuse
const OverMixed = A(B(C(D(E(Base)))));  // โš ๏ธ  hard to debug      // โš ๏ธ

// Don't use static mutable state in concurrent code
static cache = new Map();  // โš ๏ธ  shared, no locking               // โš ๏ธ

// Don't lose type info with class expressions
const C = class { x = 1 };  // type inference may be limited        // โš ๏ธ

Common Pitfalls

PitfallProblemSolution
Instance access of staticundefinedUse ClassName.member
Static access of instanceNot availableCreate instance first
Shadowing static propertyParent’s value lostUnderstand inheritance
this in static methodRefers to classUse ClassName or this deliberately
Missing new on class expressionRuntime errorAlways new
Mixin type inferenceComplex types failAdd explicit types
Shared mutable stateRace conditionsLock, or avoid
Static block timingOrder mattersRuns once at class load
typeof vs instanceWrong type usedKnow the difference
Private constructor + staticsCan’t instantiateUse factory

Real-World Examples

1. Static constant

class Config {
  static readonly MAX_RETRIES = 3;
}

2. Static counter

class User {
  static count = 0;
  constructor() { User.count++; }
}

3. Static cache

class Cache {
  private static store = new Map<string, unknown>();
  static get(k: string) { return this.store.get(k); }
}

4. Static factory

class User {
  static create(name: string): User {
    return new User(crypto.randomUUID(), name);
  }
}

5. Factory from JSON

static fromJson(s: string): User {
  const d = JSON.parse(s);
  return new User(d.id, d.name);
}

6. Singleton

class App {
  private static instance: App | null = null;
  static get(): App {
    return App.instance ??= new App();
  }
  private constructor() {}
}

7. Static block

class Config {
  static defaults: Record<string, string>;
  static {
    Config.defaults = loadEnv();
  }
}

8. Static utility class

class Strings {
  private constructor() {}
  static upper(s: string): string { return s.toUpperCase(); }
}

9. Static inheritance

class Base {
  static type = 'base';
}
class Sub extends Base {
  static type = 'sub';
}

10. Generic static factory

static create<T extends typeof Entity>(
  this: T, name: string
): InstanceType<T> {
  return new this(name) as InstanceType<T>;
}

11. Class expression โ€” anonymous

const Point = class {
  constructor(public x: number, public y: number) {}
};

12. Class expression โ€” named

const Point = class PointClass {
  constructor(public x: number, public y: number) {}
};

13. Class expression returned

const createModel = <T>(d: T) => class {
  data = { ...d };
};

14. Class passed as argument

function make<T>(C: new () => T): T {
  return new C();
}

15. typeof for constructor type

type UserCtor = typeof User;

16. InstanceType

type UserInst = InstanceType<typeof User>;

17. Mixin function

type Ctor<T = {}> = new (...args: any[]) => T;

function Identified<T extends Ctor>(B: T) {
  return class extends B {
    id = crypto.randomUUID();
  };
}

18. Applying mixins

const User = Identified(Timestamped(Entity));

19. Mixin with state

function Countable<T extends Ctor>(B: T) {
  return class extends B {
    static count = 0;
    constructor(...args: any[]) {
      super(...args);
      (this.constructor as any).count++;
    }
  };
}

20. Static block with env

class Env {
  static api: string;
  static {
    Env.api = process.env.API ?? 'http://localhost';
  }
}

Visual: Instance vs Static

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class Counter {                             โ”‚
โ”‚    static count = 0;                         โ”‚
โ”‚    value = 0;                                โ”‚
โ”‚                                              โ”‚
โ”‚    static reset() { Counter.count = 0; }     โ”‚
โ”‚    increment() { this.value++; }             โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚                              โ”‚
       โ”‚ class side                   โ”‚ instance side
       โ–ผ                              โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Counter (the class) โ”‚    โ”‚  c = new Counter()   โ”‚
โ”‚                      โ”‚    โ”‚                      โ”‚
โ”‚  Counter.count       โ”‚    โ”‚  c.value             โ”‚
โ”‚  Counter.reset()     โ”‚    โ”‚  c.increment()       โ”‚
โ”‚                      โ”‚    โ”‚                      โ”‚
โ”‚  Shared across all   โ”‚    โ”‚  Per instance        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Static Members Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class Config {                              โ”‚
โ”‚    static readonly VERSION = '1.0.0';        โ”‚
โ”‚    static load(): void { }                   โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ”‚  accessed via class
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Config.VERSION   โ†’  '1.0.0'                 โ”‚
โ”‚  Config.load()    โ†’  runs                    โ”‚
โ”‚                                              โ”‚
โ”‚  new Config().VERSION  โ†’  โŒ undefined       โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Static this in Inheritance

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class Animal {                              โ”‚
โ”‚    static species = 'unknown';               โ”‚
โ”‚    static describe() {                       โ”‚
โ”‚      return `Species: ${this.species}`;      โ”‚
โ”‚    }                                         โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                    โ–ฒ
                    โ”‚ extends
                    โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class Dog extends Animal {                  โ”‚
โ”‚    static species = 'canine';                โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Animal.describe()  โ†’  this = Animal         โ”‚
โ”‚                    โ†’  'Species: unknown'     โ”‚
โ”‚                                              โ”‚
โ”‚  Dog.describe()     โ†’  this = Dog            โ”‚
โ”‚                    โ†’  'Species: canine'      โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Factory Pattern

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class User {                                โ”‚
โ”‚    constructor(                              โ”‚
โ”‚      public id: string,                      โ”‚
โ”‚      public name: string                     โ”‚
โ”‚    ) {}                                      โ”‚
โ”‚                                              โ”‚
โ”‚    static create(name: string): User {       โ”‚
โ”‚      return new User(                        โ”‚
โ”‚        crypto.randomUUID(),                  โ”‚
โ”‚        name                                  โ”‚
โ”‚      );                                      โ”‚
โ”‚    }                                         โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ”‚  User.create('Alice')
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  { id: '<uuid>', name: 'Alice' }             โ”‚
โ”‚                                              โ”‚
โ”‚  Factory generates id, validates, returns    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Class Expression

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Declaration                                 โ”‚
โ”‚                                              โ”‚
โ”‚  class Point { }                             โ”‚
โ”‚                                              โ”‚
โ”‚  โ”€ Statement                                 โ”‚
โ”‚  โ”€ Not a value                               โ”‚
โ”‚  โ”€ Can't be passed                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Expression                                  โ”‚
โ”‚                                              โ”‚
โ”‚  const Point = class { };                    โ”‚
โ”‚                                              โ”‚
โ”‚  โ”€ Value                                     โ”‚
โ”‚  โ”€ Can be passed, returned, assigned         โ”‚
โ”‚  โ”€ Enables mixins, factories                 โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Mixin Composition

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class Entity { }                            โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ”‚  Timestamped(Entity)
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class extends Entity {                      โ”‚
โ”‚    createdAt = new Date();                   โ”‚
โ”‚    updatedAt = new Date();                   โ”‚
โ”‚    touch() { this.updatedAt = new Date(); }  โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ”‚  Identified(...)
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class extends ... {                         โ”‚
โ”‚    id = crypto.randomUUID();                 โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  Has: createdAt, updatedAt, touch, id        โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: typeof vs Instance

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class User {                                โ”‚
โ”‚    static count = 0;                         โ”‚
โ”‚    name = '';                                โ”‚
โ”‚    greet() { }                               โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  typeof User โ€” constructor type              โ”‚
โ”‚                                              โ”‚
โ”‚  {                                           โ”‚
โ”‚    new (): User;                             โ”‚
โ”‚    count: number;                            โ”‚
โ”‚    prototype: User;                          โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  Includes static members                     โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  User โ€” instance type                        โ”‚
โ”‚                                              โ”‚
โ”‚  {                                           โ”‚
โ”‚    name: string;                             โ”‚
โ”‚    greet(): void;                            โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  Includes instance members                   โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Static Block Initialization

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class Settings {                            โ”‚
โ”‚    static defaults: Record<string, string>;  โ”‚
โ”‚                                              โ”‚
โ”‚    static {                                  โ”‚
โ”‚      // runs once, at class load             โ”‚
โ”‚      const env = process.env.NODE_ENV;       โ”‚
โ”‚      Settings.defaults = env === 'prod'      โ”‚
โ”‚        ? { api: 'https://api.x' }            โ”‚
โ”‚        : { api: 'http://localhost' };        โ”‚
โ”‚    }                                         โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  Timing: once, when class is defined         โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Decision Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Does the value belong to the class,         โ”‚
โ”‚  not to any instance?                        โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ”œโ”€โ”€ Yes โ”€โ”€โ–บ Static member              โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ””โ”€โ”€ No โ”€โ”€โ–บ Instance member             โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Do you need the class as a value?           โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ”œโ”€โ”€ Yes โ”€โ”€โ–บ Class expression           โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ””โ”€โ”€ No โ”€โ”€โ–บ Class declaration           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Common Patterns

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Static members โ€” where they fit             โ”‚
โ”‚                                              โ”‚
โ”‚  Constant       โ†’  static readonly           โ”‚
โ”‚  Counter        โ†’  static property + inc     โ”‚
โ”‚  Cache          โ†’  static Map                โ”‚
โ”‚  Factory        โ†’  static method             โ”‚
โ”‚  Singleton      โ†’  static + private ctor     โ”‚
โ”‚  Utility class  โ†’  static-only + private ctorโ”‚
โ”‚  Init logic     โ†’  static { }                โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Class expressions โ€” where they fit          โ”‚
โ”‚                                              โ”‚
โ”‚  Factory        โ†’  return class              โ”‚
โ”‚  Mixin          โ†’  function returning class  โ”‚
โ”‚  Decorator      โ†’  wraps class               โ”‚
โ”‚  Passed value   โ†’  fn(class { })             โ”‚
โ”‚  Assigned       โ†’  const C = class { }       โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ConceptMeaning
Static memberLives on the class, not instances
Static propertyShared value
Static methodClass-level function
Static blockRuns once at class load
Factory methodStatic method returning instances
Static inheritanceSubclass inherits and can override
this in staticRefers to the called class
Class expressionClass as a value
MixinFunction combining classes
typeof ClassConstructor type
InstanceType<T>Instance type from constructor

Key takeaways:

  • Static members live on the class โ€” ClassName.member โ€” not on instances
  • Static properties are for shared state: constants, counters, caches, singletons
  • Static methods are for factories, utilities, and operations that don’t need instance data
  • Static blocks run once at class load โ€” for complex initialization
  • Static inheritance works, and this in static methods refers to the called class
  • Class expressions are classes as values โ€” assignable, passable, returnable
  • Factories are the classic use of statics โ€” User.create() centralizes construction
  • Mixins use class expressions to combine behaviors without multiple inheritance
  • typeof Class is the constructor type; Class is the instance type
  • InstanceType<T> extracts the instance type from a constructor type
  • Static methods can’t access instance data โ€” they have no this instance
  • Use private constructors with static methods for utility classes and singletons
  • Prefer instance members unless the value genuinely belongs to the class

Remember: Static members belong to the class, not instances โ€” constants, factories, caches, and singletons live there. Class expressions make classes first-class values โ€” usable in factories, mixins, and decorators. Together they extend what “class” means beyond a simple declaration. Use static when the value is genuinely class-level, use instance members for per-object state, and reach for class expressions when you need to pass, return, or compose classes. That’s the whole toolset โ€” class declarations for the normal case, and these two features for when the class itself becomes a value or holds shared behavior.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!