In TypeScript, what does `constructor(private readonly repo: UserRepo) {}` declare on the class, and how is that different from `constructor(repo: UserRepo) {}`?
answer
- modifier on a constructor parameter
- declares and assigns in one step
- public, private, protected or readonly
- no modifier means just a local
- shorthand for this.x = x
basics
~20 sAn accessibility or readonly modifier on a constructor parameter makes it a parameter property: TypeScript declares a matching class member and assigns the argument to it. Without a modifier the parameter stays an ordinary constructor-local.
solid answer
~40 sPutting an accessibility modifier — `public`, `private`, `protected` — or `readonly` on a constructor parameter makes it a **parameter property**. The compiler declares a class member with the same name and type and assigns the argument to it, so `constructor(private readonly repo: UserRepo) {}` is shorthand for declaring `private readonly repo: UserRepo` in the class body plus writing `this.repo = repo` in the constructor. Without a modifier, `constructor(repo: UserRepo) {}` leaves `repo` as a plain parameter that vanishes when the constructor returns — there is no `this.repo`, and reading one is a compile error. `readonly` on its own is enough to trigger the shorthand, the modifiers combine, and the parameter can still be optional or carry a default value.
code
typescript · 13 linesclass UserService {
constructor(private readonly repo: string[]) {}
first(): string | undefined { return this.repo[0]; }
}
// longhand equivalent
class UserServiceLonghand {
private readonly repo: string[];
constructor(repo: string[]) { this.repo = repo; }
first(): string | undefined { return this.repo[0]; }
}
console.log(new UserService(["a"]).first(), new UserServiceLonghand(["a"]).first());go deeper
Be able to state the rule plainly: a modifier on a constructor parameter declares the field and assigns it, no modifier means the parameter disappears when the constructor ends. Write out the longhand equivalent if asked.
Explain which modifiers trigger it — including readonly on its own — and where it is rejected: rest parameters, binding patterns, and signatures without a body. Mention that defaults and optionality still apply to the parameter.
Be ready to say why teams reach for it: constructor-injection code repeats every dependency three times, and the shorthand removes two of them. Note the cost — the class's state is no longer visible in the class body.
Own the convention question. Decide whether a codebase uses the shorthand everywhere, nowhere, or only for injected dependencies, and be able to justify the rule in terms of readability, review load, and what the build pipeline can compile.
## The shorthand in one sentence A **parameter property** is a constructor parameter that also declares a class member. You write the modifier on the parameter, and TypeScript does two things for you: it adds the member declaration to the class type, and it emits an assignment from the argument to `this` at the start of the constructor body. ```ts class UserService { constructor(private readonly repo: UserRepo) {} find(id: string) { return this.repo.get(id); } } ``` The longhand of exactly that class is: ```ts class UserService { private readonly repo: UserRepo; constructor(repo: UserRepo) { this.repo = repo; } find(id: string) { return this.repo.get(id); } } ``` The two are interchangeable to callers: `new UserService(repo)` behaves the same either way. ## Which modifiers turn a parameter into a property Any one of these on the parameter is enough: - `public`, `private`, `protected` — the accessibility modifiers; - `readonly` — on its own, `constructor(readonly id: string) {}` is a valid parameter property with default (public) visibility; - `override` — allowed when the class extends a base that already declares the member. They combine in the usual order: `private readonly`, `protected override`. A parameter with only a type annotation, `constructor(repo: UserRepo) {}`, is **not** a parameter property. That distinction is the whole question: if there is no modifier, there is no member. Referring to `this.repo` in such a class produces `Property 'repo' does not exist on type 'UserService'`. ## The parameter is still a normal parameter Defaults and optionality still work, and they apply to the parameter, which then flows into the member: ```ts class Tag { constructor(private name: string = "anon", readonly count?: number) {} } ``` Here `name` has type `string` (the default removes `undefined` from what callers must pass), and `count` is `number | undefined` because it is optional. Inside the constructor body you may still read the bare parameter name — `name` — as well as `this.name`; before the emitted assignment runs they hold the same value. ## Where the shorthand is not allowed Three restrictions come up in interviews, and each has a specific compiler error: - **Rest parameters.** `constructor(private ...tags: string[]) {}` fails with `A parameter property cannot be declared using a rest parameter` (TS1317). - **Destructuring patterns.** `constructor(private { x }: Point) {}` fails with `A parameter property may not be declared using a binding pattern` (TS1187). If you want to destructure, take the object as one parameter property and pull fields out in the body. - **Signatures without a body.** On an overload signature or inside a `declare class`, you get `A parameter property is only allowed in a constructor implementation` (TS2369). Overloads are fine as long as the modifier lives only on the implementation signature. ## Why anyone uses it The shorthand exists because the longhand repeats each dependency three times — declaration, parameter, assignment — and constructor-injection frameworks such as Angular and NestJS produce classes that are almost entirely dependencies. Collapsing three lines per dependency into one is a real readability win in that style of code. The cost is that a reader can no longer see the class's state by scanning the top of the class body; it is hidden in the constructor signature. ## What it does not change The modifier means exactly what it means on a normal member. `private` and `protected` are checked by the compiler and disappear from the emitted JavaScript, `readonly` stops later assignments at compile time only, and neither adds any runtime enforcement. Parameter properties are a declaration shorthand, not a new kind of member: the resulting class type is identical to the longhand version, so `implements` checks, subclassing, and structural assignability all behave the same. ## How to answer it in an interview Say what the modifier does (declares plus assigns), show the longhand equivalent, and state the negative case out loud: no modifier, no property. If you have room, add that `readonly` alone is enough and that rest parameters and binding patterns are excluded. That answer covers everything an interviewer is checking for at this level.
- Is `readonly` on its own enough, or does a parameter property need an accessibility modifier too?`readonly` alone is enough. `constructor(readonly id: string) {}` declares a public readonly member `id` and assigns the argument to it. The accessibility modifiers are independent: you may write `private`, `protected`, or `public` alone, `readonly` alone, or the two combined as `private readonly`. Only a parameter with no modifier at all stays a plain constructor-local.
- Can you write `constructor(private ...tags: string[]) {}`?No. TypeScript rejects it with TS1317, `A parameter property cannot be declared using a rest parameter`. The same goes for destructuring: `constructor(private { x }: Point) {}` is TS1187, `A parameter property may not be declared using a binding pattern`. If you need either shape, take the rest or the object as a normal parameter and assign to an explicitly declared field in the constructor body.
- If a class has constructor overloads, where does the modifier go?Only on the implementation signature. A modifier on an overload signature — or on a constructor inside a `declare class`, which likewise has no body — is TS2369, `A parameter property is only allowed in a constructor implementation`. Overload signatures declare the callable shapes; the implementation is the one that actually runs and can therefore carry the generated assignment.
saying these in an interview costs you the question
- Claims every constructor parameter becomes an instance property
- Thinks readonly alone cannot create a parameter property
- Says the shorthand works only with private
- Assumes a rest parameter can be a parameter property
- Believes the private modifier blocks access at runtime