TypeScript Parameter Properties

In ordinary JavaScript classes, giving a class a field usually means writing the field name in a constructor parameter, then writing a separate this.field = field; assignment for every single one. TypeScript’s parameter properties collapse that pattern into one step: by adding an accessibility modifier (public, private, protected) or readonly directly in front of a constructor parameter, you simultaneously declare a class property and assign it from the argument. It is pure syntactic sugar, but it is one of the most-used features in real TypeScript codebases because it removes so much repetitive code.

Overview: What Parameter Properties Are

Normally, declaring a typed class field requires two places to write the name: once as a class member declaration, and once as a constructor parameter that gets assigned to it. For example, a plain (non-parameter-property) version of a class looks like this:

class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } }

Every property is mentioned three times: as a field declaration, as a constructor parameter, and as an assignment. TypeScript lets you skip the field declaration and the assignment entirely by placing a modifier keyword directly before the parameter name in the constructor signature. When the compiler sees public, private, protected, or readonly attached to a constructor parameter, it does two things: it declares a class property with that name, type, and accessibility, and it inserts a this.name = name; assignment at the very top of the compiled constructor body. This only works on constructor parameters — you cannot add these modifiers to parameters of ordinary methods or standalone functions, because only a constructor has a natural place (this) to assign the field onto.

Parameter properties do not change what your program does at runtime; they change how much you have to type to express it. Two classes — one written with parameter properties, one written the long way — compile to functionally equivalent JavaScript.

Syntax

The general form is a modifier keyword written directly before a constructor parameter’s name:

constructor(modifier parameterName: Type, ...) { }

Modifier Meaning
public Accessible from anywhere (the default if you omit a modifier on a normal field, but must be written explicitly to trigger parameter-property behavior)
private Accessible only inside the declaring class (compile-time check only — erased at runtime)
protected Accessible inside the declaring class and its subclasses
readonly Can only be assigned once, in the constructor; combine with an accessibility modifier, e.g. public readonly or private readonly

Rules to keep in mind:

  • At least one modifier is required for a parameter to become a property — a parameter with no modifier stays a plain, non-property parameter.
  • Modifiers only apply in a constructor’s implementation signature, never in constructor overload signatures or in ordinary method/function parameter lists.
  • You can freely mix parameter properties with plain parameters in the same constructor.
  • The property’s declared type is taken directly from the parameter’s type annotation.

Examples

Example 1: Basic parameter properties

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

const p = new Point(3, 4);
console.log(`(${p.x}, ${p.y})`);

Output:

(3, 4)

Both x and y are declared and assigned in one line each. There is no separate field declaration and no explicit this.x = x anywhere in the source — TypeScript generates that assignment for you.

Example 2: Mixing accessibility modifiers and readonly

class BankAccount {
  constructor(
    public readonly accountId: string,
    private balance: number,
    protected owner: string
  ) {}

  deposit(amount: number): void {
    this.balance += amount;
    console.log(`Deposited ${amount}. New balance: ${this.balance}`);
  }

  getBalance(): number {
    return this.balance;
  }
}

const acct = new BankAccount("ACC-001", 100, "Alice");
acct.deposit(50);
console.log(acct.getBalance());

Output:

Deposited 50. New balance: 150
150

This one constructor signature declares three properties with three different access rules: accountId is public and can never be reassigned after construction, balance is private and only mutable from inside BankAccount‘s own methods, and owner is protected so a subclass could still read or change it. Trying to write acct.balance from outside the class would fail to type-check with a "Property ‘balance’ is private" error.

Example 3: Parameter properties with inheritance and defaults

class Employee {
  constructor(
    public readonly id: number,
    public name: string,
    private salary: number = 50000
  ) {}

  giveRaise(amount: number): void {
    this.salary += amount;
    console.log(`${this.name}'s new salary: ${this.salary}`);
  }
}

class Manager extends Employee {
  constructor(
    id: number,
    name: string,
    salary: number,
    public teamSize: number
  ) {
    super(id, name, salary);
  }
}

const mgr = new Manager(1, "Bob", 80000, 5);
mgr.giveRaise(5000);
console.log(`${mgr.name} manages ${mgr.teamSize} people`);

Output:

Bob's new salary: 85000
Bob manages 5 people

Notice that Employee‘s constructor parameters (id, name, salary) all use parameter-property shorthand, including a default value on salary, while Manager‘s constructor takes id, name, and salary as plain parameters (no modifiers, just passed through to super()) and only uses the shorthand for its own new property, teamSize. You can mix parameter properties and plain parameters freely in the same parameter list, and a subclass is free to add its own parameter properties independently of what the base class declared.

Under the Hood: How Parameter Properties Compile

Parameter properties exist only at compile time. Once tsc emits JavaScript, all type annotations and accessibility modifiers are erased, and what remains is an ordinary class field with an ordinary assignment. Conceptually, TypeScript rewrites constructor(public x: number) {} into something equivalent to:

constructor(x) { this.x = x; }

The compiler inserts these assignment statements at the very start of the constructor body. In a derived class, this insertion happens right after the super() call, since this cannot be referenced before the base class constructor has run — the same rule that applies to any other use of this in a subclass constructor. Because the modifiers are erased, the resulting JavaScript property is a completely normal, publicly readable and writable object property, regardless of whether you wrote public, private, or protected in the TypeScript source. Privacy in TypeScript (using the modifier keywords, as opposed to true JavaScript #private fields) is a compile-time-only guarantee: it stops other TypeScript code from accessing the field through the type checker, but at runtime, or from plain JavaScript calling into your compiled output, the field is fully accessible. If you need privacy enforced by the JavaScript runtime itself, use a real private field name (#balance) instead of the private modifier — though note that #-prefixed fields cannot be declared as parameter properties, since parameter-property shorthand only supports the modifier keywords.

Common Mistakes

Mistake 1: Forgetting the modifier, then trying to use this

A constructor parameter with no modifier at all is just a parameter — it is never turned into a property, so accessing it via this elsewhere in the class fails to type-check:

class Widget {
  constructor(label: string) {
    // no modifier here, so `label` is just a parameter, not a property
  }

  showLabel(): void {
    console.log(this.label); // Error: Property 'label' does not exist on type 'Widget'.
  }
}

tsc reports: Property 'label' does not exist on type 'Widget'. The fix is to add an accessibility modifier so the parameter also becomes a declared, assigned property:

class Widget {
  constructor(public label: string) {}

  showLabel(): void {
    console.log(this.label);
  }
}

const w = new Widget("Save");
w.showLabel();

Output:

Save

Mistake 2: Putting a modifier on a constructor overload signature

When a class declares constructor overloads, modifiers are only legal on the final, implementation signature — not on any of the overload declarations above it:

class Container<T> {
  constructor(public value: T); // Error: A parameter property is only allowed in a constructor implementation.
  constructor(value: T, label: string) {
  }
}

tsc reports: A parameter property is only allowed in a constructor implementation. Move the modifier down to the actual implementation signature (the last one, which has a body):

class Container<T> {
  constructor(value: T);
  constructor(value: T, label: string);
  constructor(public value: T, label?: string) {
    if (label) {
      console.log(`${label}: ${this.value}`);
    }
  }
}

const c1 = new Container<number>(42);
const c2 = new Container<string>("hello", "greeting");
console.log(c1.value);
console.log(c2.value);

Output:

greeting: hello
42
hello

Best Practices

  • Use parameter properties by default for simple data-holding constructors — they cut boilerplate and keep the property list and constructor signature in perfect sync.
  • Prefer readonly on any property that should never change after construction; it turns an entire class of accidental-mutation bugs into compile-time errors.
  • Don’t mix parameter properties with a separate, redundant field declaration for the same name — that causes a duplicate-declaration error, since the parameter property already declares the field.
  • Remember that private/protected on parameter properties are compile-time-only; don’t rely on them to hide genuinely sensitive data from runtime code, use real #private fields for that.
  • When a constructor is getting long or has many non-property parameters mixed with property ones, consider whether an options object (destructured, then assigned manually) would be clearer than a long parameter-property list.
  • In a subclass, only give a parameter a modifier if you want to introduce a new property; parameters that just forward values to super() should stay plain.

Practice Exercises

  • Write a Rectangle class using parameter properties for two public readonly numeric fields, width and height. Add an area() method that returns width * height. Construct a Rectangle with width 4 and height 5, and log the area (expected output: 20).
  • Take a class that manually declares fields and assigns them in the constructor body (three fields, three assignments) and rewrite it using parameter-property shorthand so the constructor body becomes empty ({}). Confirm behavior is unchanged by logging the same values before and after.
  • Create a base class Logger with a protected parameter property prefix: string. Create a subclass ErrorLogger extends Logger that adds its own public readonly parameter property errorCode: number, plus a log(message: string) method that prints [prefix] message (code: errorCode) using the inherited prefix and the subclass’s own errorCode.

Summary

  • Parameter properties let you declare and assign a class property in one place, by adding public, private, protected, and/or readonly directly before a constructor parameter.
  • A parameter with no modifier remains a plain parameter and is never accessible via this.
  • The compiler inserts a this.name = name; assignment at the top of the constructor (after super() in derived classes) and erases all modifiers — the runtime field is an ordinary, fully accessible JavaScript property.
  • Modifiers are only legal on the constructor’s implementation signature, never on overload signatures or on ordinary function/method parameters.
  • private/protected enforce access rules only at compile time; use real #private fields when you need runtime-enforced privacy.
  • Parameter properties and plain parameters can be freely combined in the same constructor, including across base and derived classes.