A TypeScript codebase declares every service dependency as a constructor parameter property, as in `constructor(private readonly repo: Repo, private readonly clock: Clock) {}`. As the lead, when do you keep that convention and when do you require explicit field declarations instead?
answer
- shorthand removes triple repetition
- state disappears from the class body
- hides an over-injected constructor
- erasable-syntax pipelines forbid it
- one rule per codebase, enforced by lint
basics
~20 sKeep the shorthand where classes are mostly injected dependencies and the constructor does nothing else, since it removes two of the three repetitions per dependency. Require explicit fields when values are derived, validated, or when the build must run on erasable syntax only.
solid answer
~50 sI treat it as a per-codebase convention, not a per-file preference. The shorthand earns its place in constructor-injection code — Angular or NestJS services whose constructors do nothing but accept collaborators — because the longhand repeats each dependency three times and reviewers end up reading boilerplate. I move to explicit fields when the constructor does real work: when a stored value is *derived* from an argument rather than the argument itself, when arguments need validation or normalisation, or when the class is large enough that a reader deserves a visible field list at the top instead of a signature to parse. Two hard constraints override taste: field initializers that read parameter properties are ordering-sensitive, and the syntax is non-erasable, so a pipeline built on type-stripping cannot use it at all. Whichever way it goes, one rule, enforced by lint, beats mixed styles.
go deeper
Be able to name one benefit and one cost: less repetition per dependency, but the fields are no longer visible at the top of the class. That is enough at this level.
Explain the mechanics behind the tradeoff — three mentions collapsing to one, and the fact that the shorthand only works when the field stores the argument unchanged under the same name.
Show judgment with a boundary a team can apply: shorthand for injected collaborators, explicit fields whenever the constructor derives, validates, or renames. Mention the ordering hazard and the erasability constraint.
Own it as an architectural decision with enforcement and a migration path: pick a default, encode it in lint, and say how you would keep the option of type-stripping builds open across the whole codebase.
## Frame it as a convention decision The question is not "is the shorthand good" — it is what a whole codebase should do consistently, because the real cost of this feature is inconsistency. A class where two dependencies are parameter properties and a third is an explicit field forces every reader to check both places for state. The valuable answer picks a default, names the exceptions, and says how the rule is enforced. ## What the shorthand buys The longhand mentions each dependency three times: the field declaration, the constructor parameter, and the assignment. In constructor-injection frameworks a service class is often *nothing but* dependencies, so a five-dependency class costs fifteen lines of pure mechanism. The shorthand cuts it to five. The secondary benefit is that the three mentions cannot drift apart. There is no way to add a parameter and forget the assignment, or to rename the field and leave a stale declaration — a class of small, boring bugs that reviewers otherwise have to catch by eye. Adding a dependency becomes a one-line diff, which also keeps code review focused on the change rather than on the plumbing around it. ## What it costs **The class no longer advertises its state.** Scanning the top of a class body normally tells you what an instance holds. With parameter properties, that information moves into the constructor signature, mixed together with argument order, defaults, and any parameter decorators the framework requires. Past three or four dependencies the signature becomes a paragraph. **It hides a design smell.** A constructor taking eight collaborators is a class doing too much, and the longhand makes that obvious — eight declarations at the top of the file are hard to ignore. The shorthand makes an over-injected class look tidy, which removes the pressure to split it. **Ordering hazards.** Because the generated assignment lives in the constructor body, a field initializer elsewhere in the class can read a parameter property before it holds anything, depending on how class fields are compiled. Explicit fields plus explicit constructor statements put the order in front of the reader, where it can be reasoned about. **Erasability.** The shorthand generates code rather than being erased, so it is incompatible with any pipeline that runs TypeScript by stripping type syntax; the compiler's `erasableSyntaxOnly` option rejects it outright. If a team wants that option open — now or later — the convention has to be explicit fields, and the decision is architectural rather than stylistic. ## When to keep it Keep it where all of these hold: the constructor body is empty or nearly so; every parameter is stored as-is under the same name; the class exists inside a framework whose whole idiom is constructor injection; and the build compiles with `tsc` or an equivalent that fully supports the syntax. That describes the bulk of service and controller classes in an Angular or NestJS application, and fighting the framework's idiom there costs more than it saves. ## When to require explicit fields Switch as soon as the constructor stops being a pass-through: - **Derived values.** If the field is `new Set(items)` or `items.map(normalize)` rather than `items`, there is no shorthand to use — write the field and the statement. - **Validation or normalisation.** Anything that throws, clamps, defaults, or logs belongs in a body that a reader can see. - **Naming divergence.** When the parameter and the field want different names, the shorthand is not an option. - **Domain classes with meaningful state.** Value objects and entities are read far more often than they are constructed; the field list is the documentation. - **Runtime-private state.** JavaScript `#` fields cannot be written as parameter properties. ## Mixed policies that work A common and defensible middle ground: parameter properties for injected collaborators, explicit fields for domain state. It is easy to state — "if the framework supplies it, use the shorthand; if the class owns it, declare it" — and it survives contact with reviewers because the boundary is objective. A second workable rule is a numeric cap: shorthand up to N dependencies, explicit fields beyond, on the theory that the signature stops being readable somewhere around four. ## Enforcement and migration Whatever the rule, encode it. typescript-eslint ships a rule dedicated to parameter properties that can require or forbid the form, so the convention lives in configuration rather than in review comments. If the direction of travel is toward erasable syntax, turn on `erasableSyntaxOnly` in a leaf project first, fix what it reports, and expand outward — the rewrite is mechanical and local, with no change to any class's public type, so it can proceed package by package without a flag day. ## What a strong answer sounds like It refuses the framing of personal taste. It names the concrete benefit (three mentions to one, no drift), the concrete costs (invisible state, hidden over-injection, ordering, erasability), gives a rule with an objective boundary, and closes with enforcement. A weak answer argues that the shorthand is "cleaner" and stops there, with no account of what a future reader or a future build tool pays for it.
- Does using parameter properties make a class harder to unit test?Not in itself — the constructor signature is identical either way, so a test still passes collaborators positionally. The testing pain comes from how many dependencies there are, not how they are declared, and the shorthand's real sin is making a long dependency list look short enough to ignore. If tests are painful to set up, split the class rather than change the declaration style.
- How would you migrate a large codebase away from parameter properties?Mechanically and incrementally. The rewrite is local — declare the field, assign it in the constructor — and it changes no class's public type, so callers, subclasses and `implements` checks are untouched. Turn `erasableSyntaxOnly` on in one leaf package, fix what it reports, then expand package by package, with a lint rule holding the line behind you.
- Would you allow both styles in one class?No. Splitting a class's state between the constructor signature and the class body means every reader has to check two places to know what an instance holds, which is exactly the cost the shorthand was supposed to avoid. If a class needs one explicit field for a derived value, I would rather it declare all of them explicitly and keep the whole class readable in one place.
saying these in an interview costs you the question
- Argues the shorthand is purely cosmetic with no emitted code
- Claims dependency injection requires parameter properties
- Ignores that the class's state is no longer visible in the body
- Says the choice never affects build tooling or pipelines
- Allows both styles in one class as a matter of taste