In Dart 3.12 and later, what does a private named parameter like {required this._currency} do, and where is it not allowed?
answer
- public argument, private field
- the underscore is stripped for callers
- initializing formals only
- no underscore-only names
- the old initializer-list boilerplate
basics
~20 sSince Dart 3.12 a constructor can write {required this._currency}: the field stays private while callers pass currency:. It works only for initializing formals, not for ordinary named parameters, and replaces an initializer list that just stripped the underscore.
solid answer
~40 sBefore 3.12 a named parameter could not start with an underscore, so a private field needed `PriceFormatter({required String currency}) : _currency = currency;`. Dart 3.12 lets you write `PriceFormatter({required this._currency, this._decimals = 2})`: the compiler strips the underscore, so callers write `currency:` and `decimals:`, while the fields stay library-private. Like any named parameter it can be optional, required or defaulted; in the initializer list you use the private name, and a subclass forwards it with the public name, `super.currency`. Limits: it applies only to initializing formals, so `void f({String? _x})` is still an error; the public name must be a valid identifier, so `this._` is out; it must not clash with another parameter; and the package needs language version 3.12 or later.
code
dart · 20 linesclass PriceFormatter {
PriceFormatter({required this._currency, this._decimals = 2})
: assert(_decimals >= 0);
final String _currency;
final int _decimals;
String format(num amount) =>
'${amount.toStringAsFixed(_decimals)} $_currency';
}
class WholeUnitFormatter extends PriceFormatter {
WholeUnitFormatter({required super.currency}) : super(decimals: 0);
}
void main() {
final eur = PriceFormatter(currency: 'EUR');
print(eur.format(12.5)); // 12.50 EUR
print(WholeUnitFormatter(currency: 'JPY').format(1200)); // 1200 JPY
}go deeper
Recall that {required this._x} lets callers write x: while the field stays private, and that it needs Dart 3.12.
Explain that the underscore is stripped only for initializing formals, how super parameters use the public name, and the naming constraints.
Plan the migration: raise the SDK lower bound, use dart fix for prefer_initializing_formals, and review that no field was meant to be public.
Weigh encapsulation against simplicity in shared models: private fields with public constructor names versus plain public final fields.
## The problem it solves Dart makes a field **private to its library** by starting its name with an underscore: `_currency`. Until Dart 3.12, a named parameter could not start with an underscore, so a constructor that wanted a **public argument name** but a **private field** needed boilerplate: ```dart class PriceFormatter { final String _currency; PriceFormatter({required String currency}) : _currency = currency; } ``` The initializer list exists only to strip the underscore. ## What Dart 3.12 added With a language version of 3.12 or later, a named **initializing formal** can use the private name directly, and the compiler strips the underscore for callers: ```dart class PriceFormatter { final String _currency; final int _decimals; PriceFormatter({required this._currency, this._decimals = 2}); } final fmt = PriceFormatter(currency: 'EUR', decimals: 0); ``` - Callers write the **public name** `currency:`; the field stays `_currency`. - The parameter can be **optional or required**, and it can have a **default value**, like any named parameter. - Inside the constructor's initializer list, you refer to it by its **private name**, for example `: assert(_decimals >= 0)`. - A subclass forwarding it with a super parameter uses the **public name**: `Money({required super.currency})`. ## The constraints | Rule | Example that fails | |---|---| | Only for **initializing formals** (`this._field`) | `void f({String? _name})` on a function stays an error | | The public name must be a **valid identifier** | `this._` or `this._2x` have no public counterpart | | **No conflicts** with other parameter names | `{this._x, int? x}` in the same constructor | | Needs **language version 3.12+** | a package whose SDK lower bound is below 3.12 | The first row is the key interview point: named parameters in general still cannot be private. The feature is an exception for parameters that exist only to initialise a private field. ## Tooling around it - The `prefer_initializing_formals` lint, in the `recommended` and `flutter` sets, now highlights named parameters that could become private named parameters, and `dart fix --code=prefer_initializing_formals` rewrites them. - Dart 3.13's **primary constructors** build on the same idea: parameters in the class header can declare fields directly. That is a separate feature; the private-named-parameter rules above still apply to ordinary constructors. ## When to use it 1. **Immutable value classes and formatters** with private state and a public constructor API: the scenario above. 2. **Flutter widgets** whose configuration is stored in private fields for encapsulation while the constructor keeps public argument names. 3. **Migrating older code**: delete initializer lists whose only job was `_x = x`, which removes a class of copy-paste mistakes where the wrong field was assigned. Do not reach for it when the field should simply be public; a public `final` field with `this.currency` stays the simplest option. ## Common mistakes - **Writing the underscore at the call site**: `PriceFormatter(_currency: 'EUR')` fails; callers only ever see `currency`. - **Expecting it on functions**: a top-level or instance method still cannot have a named parameter called `_label`; the exception exists only because `this._field` names a private field. - **Forgetting the language version**: a package whose SDK lower bound is below 3.12 keeps getting the old error even when built with a newer SDK, because the feature is gated on the package's language version, not on the installed SDK. - **Forwarding with the private name**: a subclass writes `super.currency`, never `super._currency`. ## What an interviewer listens for - That callers see the public name while the field is private. - That it works only for `this._field` initializing formals, not for arbitrary named parameters. - The version requirement, 3.12. - The before-and-after: an initializer list that existed only to strip the underscore.
- How does a Dart subclass forward a private named parameter of its superclass?It uses the public name in a super parameter: if the base class declares `Tool({required this._price})`, the subclass writes `Hammer({required super.price})`. The public name is the one callers and subclasses see; only the base class's own initializer list refers to `_price`.
- What did Dart code look like before private named parameters, and how do you migrate it?It declared a public-named parameter and copied it in the initializer list, `Point({required double x}) : _x = x;`. Once the package's SDK lower bound is 3.12 or later, `prefer_initializing_formals` flags these, and `dart fix --code=prefer_initializing_formals` can rewrite them to `Point({required this._x})`.
saying these in an interview costs you the question
- Thinks callers must now write the underscore name
- Believes any named parameter can now be private
- Says the field becomes public
- Uses super._price in a subclass
- Assumes it works on Dart versions before 3.12