skip to content

In Dart 3.7 and later, what does naming a local variable or parameter _ do, and what convention did it replace?

level: middleimportance: nice to knowfreq 18%

answer

  1. non-binding placeholder
  2. initializer still runs
  3. several _ in one scope
  4. language version 3.7 gate
  5. unnecessary_underscores lint

basics

~20 s

Since Dart language version 3.7, a local variable or parameter named _ is a non-binding wildcard: any initializer still runs, but the value cannot be read, and several _ may share one scope. It replaces the old _, __, ___ naming convention.

solid answer

~40 s

A **wildcard variable** `_` declares a placeholder that does not bind a name. Its initializer, if any, still executes, but the value is not accessible, and multiple declarations named `_` in the same scope do not collide. It is allowed for local variables, for-in loop variables, catch clause parameters, function and closure parameters, and type parameters. It is not a wildcard at top level or for class members, where the name would affect library privacy. Before 3.7, `_` was an ordinary identifier, so a callback ignoring two arguments had to write `(_, __)`. Now `(_, _)` works, and the `unnecessary_underscores` lint, in the recommended and flutter sets, points out the old `__` names. The feature is gated by the language version, so a package needs an SDK constraint lower bound of 3.7 or higher.

code

dart · 23 lines
dart
// Requires a language version of 3.7 or later (pubspec: sdk: ^3.7.0).
int warmUp() {
  print('warming up');
  return 1;
}

void main() {
  const factors = {'mi': 1609.344, 'ft': 0.3048};
  var total = 0.0;
  factors.forEach((_, factor) => total += factor); // key ignored

  void onConverted(String _, double _) => print('converted'); // no name clash
  onConverted('mi', 1.0);

  try {
    double.parse('abc');
  } on FormatException catch (_) {
    print('not a number');
  }

  var _ = warmUp(); // initializer still runs and prints
  print(total);
}

go deeper

for a junior

Recall that _ marks a value you intend to ignore, and that several _ can appear in one parameter list.

for a middle

Explain non-binding semantics, where _ is and is not a wildcard, and the language-version gate.

for a senior

Plan the SDK constraint bump that enables wildcards and fix code that used to read _ as a normal name.

for a principal

Time language-version upgrades across shared packages so small features like wildcards do not break dependants.

## What a wildcard variable is Many APIs pass arguments you do not need: `Map.forEach` gives a key and a value, a `catch` clause gives an error, a callback receives several values. Before Dart 3.7 you still had to name them, and because two parameters cannot share a name, code ended up with `_`, `__` and `___`. Dart 3.7 added **wildcard variables**. A local variable or parameter named `_` is **non-binding**: it is a placeholder. dart.dev spells out the rules: - the initializer, if there is one, **still executes**; - the value is **not accessible** afterwards; - **multiple** declarations named `_` can exist in the same namespace without a collision error. ## Where _ is a wildcard | Position | Example | |---|---| | Local variable | `var _ = warmUpCache();` | | For-in loop variable | `for (var _ in samples) count++;` | | Catch clause parameter | `catch (_) { ... }` | | Function and closure parameters | `factors.forEach((_, factor) => ...)` | | Function-typed parameters and typedefs | `typedef Handler = void Function(String _, String _);` | | Type parameters | `class Box<_> {}` | It is **not** a wildcard for top-level declarations or class members, where the leading underscore already means library privacy; those remain ordinary private names. ## Version gating Wildcards depend on the package's **language version**, which comes from the lower bound of the SDK constraint in `pubspec.yaml`. With `sdk: ^3.7.0` or higher, `_` is a wildcard. A package whose constraint starts below 3.7 keeps the old meaning, in which `_` is a normal identifier you can read: `list.map((_) => _ * 2)` compiled there and would not compile in a 3.7 package. Upgrading the constraint can therefore turn such code into errors, which is worth checking during migration. ## Replacing the old convention ```dart // Before 3.7 converter.onResult((__, ___, value) => print(value)); // 3.7 and later converter.onResult((_, _, value) => print(value)); ``` The `unnecessary_underscores` lint, stable since 3.7 and included in the recommended and flutter lint sets, flags `__`-style names that a single `_` can now replace. ## Practical uses 1. **Ignoring callback arguments**: `factors.forEach((_, factor) => total += factor);` in a unit-conversion helper that only sums values. 2. **Counting iterations**: `for (var _ in readings) samples++;` without an unused-variable warning. 3. **Catching without inspecting**: `catch (_)` when the fallback does not depend on the error, although `on FormatException catch (_)` is usually better than catching everything. 4. **Running for side effects**: `var _ = register();` documents that the result is intentionally dropped, though calling `register();` as a statement does the same. ## Migrating a package to wildcards 1. Raise the SDK constraint lower bound to `^3.7.0` or later in `pubspec.yaml`. 2. Run `dart analyze`: any code that **read** a parameter or local named `_` now fails and needs a real name. 3. Enable or keep the recommended lints so `unnecessary_underscores` flags `__` and `___` that can become `_`. 4. Review `catch (_)` blocks while you are there; catching everything is often broader than intended. The change is small, but it is a language-version bump, so it is worth doing deliberately rather than as a side effect of another upgrade. ## Boundaries The `_` in patterns, such as `case (_, 0):` or `var (x, _) = pair;`, is the **wildcard pattern** from the records and patterns feature; it predates 3.7 and is covered with patterns. The leading underscore on top-level names and members, which makes them library-private, is unrelated to wildcard variables.

  • Is the _ in var (x, _) = pair; a wildcard variable in Dart?
    It is the wildcard pattern from Dart 3.0's patterns, which also matches without binding. Wildcard variables in 3.7 extended the same idea to ordinary local declarations and parameters, which previously treated `_` as a normal name.
  • Why is a top-level variable named _ not a wildcard in Dart?
    At top level and for class members, a leading underscore already means library-private, and those declarations can be referenced from elsewhere in the library. dart.dev excludes such declarations, so `_` there remains an ordinary private name.

saying these in an interview costs you the question

  • var _ = compute(); skips calling compute because the value is unused
  • Two parameters named _ in one function are a duplicate-name error in Dart 3.7
  • _ is a wildcard in every package, whatever its SDK constraint
  • A top-level variable named _ is also a non-binding wildcard
  • You can still read _ after declaring it as a wildcard