In a Dart or Flutter project, what does dart run build_runner build do, and when do you use watch or clean instead?
answer
- builders are dev dependencies
- one pass versus a file watcher
- part directive must already exist
- cache lives in .dart_tool/build
- -d has been ignored since 2.7
basics
~20 sbuild_runner build runs every applicable builder once and writes outputs such as .g.dart parts beside your sources; watch keeps running and rebuilds incrementally on each save; clean deletes the build cache so the next build is a full one.
solid answer
~40 s`build_runner` drives **builders**: code generators such as `json_serializable`, `freezed` or `mockito`, added as dev dependencies. `dart run build_runner build` runs them once over the package and writes outputs like `person.g.dart`, which the source pulls in with `part 'person.g.dart';`. `dart run build_runner watch` stays running and rebuilds only what changed each time you save, so generated `fromJson` or `copyWith` code tracks your edits. `dart run build_runner clean` deletes the package's build cache under `.dart_tool/build`; the next build then starts from scratch, which helps when a build behaves oddly. `stop` ends a running `watch`. The old `-d`/`--delete-conflicting-outputs` flag has been ignored since build_runner 2.7: conflicting generated files are always deleted, and since 2.16 a hand-edited generated file is overwritten on the next build.
code
bash · 5 linesdart pub get
dart run build_runner build # one pass, writes *.g.dart
dart run build_runner watch # rebuild on every save
dart run build_runner stop # end a running watch
dart run build_runner clean # drop .dart_tool/build cachego deeper
Recall the three commands, the part directive the source must declare, and that annotations go in dependencies while generators go in dev_dependencies.
Explain incremental rebuilds in watch, where the cache lives, and why -d stopped mattering in build_runner 2.7.
Show you can unstick a broken build: clean, check part names and generator versions, and know the 2.16 overwrite behaviour.
Weigh how much codegen a project should depend on, given every developer and CI job must run build_runner before the code compiles.
## What build_runner is Dart has no run-time reflection in Flutter apps (`dart:mirrors` is disabled there) and no macros, so boilerplate such as JSON mapping, `copyWith`, value equality or test mocks is **generated ahead of time**. `build_runner` is the command-line driver for that generation. The generators themselves are called **builders** and ship in ordinary pub packages: - `json_serializable`: `fromJson`/`toJson` for annotated classes; - `freezed`: immutable data classes, `copyWith`, equality; - `mockito`: mock classes for tests; - `riverpod_generator`, `go_router_builder`, `drift_dev` and many others. A typical setup puts the **annotation** package in `dependencies` (it is used by your code at run time) and `build_runner` plus the **generator** in `dev_dependencies` (they only run on your machine or CI): ```yaml dependencies: json_annotation: ^4.9.0 dev_dependencies: build_runner: ^2.16.0 json_serializable: ^6.10.0 ``` Your source declares the part it expects, and references generated members: ```dart import 'package:json_annotation/json_annotation.dart'; part 'person.g.dart'; @JsonSerializable() class Person { Person({required this.name}); final String name; factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json); Map<String, dynamic> toJson() => _$PersonToJson(this); } ``` Until the first build runs, `_$PersonFromJson` does not exist and the analyzer shows errors. That is normal. ## The commands | Command | What it does | Typical use | |---|---|---| | `dart run build_runner build` | one full or incremental build, then exit | after pulling changes, in CI | | `dart run build_runner watch` | builds, then rebuilds on every file change | while editing models | | `dart run build_runner clean` | deletes the package or workspace build cache | when a build is confused | | `dart run build_runner stop` | ends a running `watch` or `serve` | freeing the lock | | `dart run build_runner serve` / `test` | build plus a dev server or test run | web and test workflows | `watch` is **incremental**: it tracks which inputs each output was built from and reruns only the builders whose inputs changed. Since build_runner 2.9 it also picks up `build.yaml` changes without restarting. ## Where files go - **Package path**: outputs such as `lib/models/person.g.dart` land next to their inputs, visible to the analyzer, compilers and your IDE. - **Artifact tree**: internal and hidden outputs live under `.dart_tool/build/generated`, and the rest of `.dart_tool/build` holds the build's cache and graph. These are private to `build_runner`; git should ignore `.dart_tool`. If a source file annotates a class but lacks the matching `part` directive, source_gen logs a warning telling you which `part` line to add, and no generated file is written for that library. ## Flags you will meet in old tutorials 1. **`-d` / `--delete-conflicting-outputs`**: before build_runner 2.7, if a generated file already existed that the build had not produced itself, the tool prompted or refused, and `-d` told it to delete such files. Since 2.7.0 the flag is **ignored**: build_runner always deletes conflicting outputs as if `-d` were passed, and the interactive prompt is gone. Scripts that still pass it keep working. 2. **`flutter pub run build_runner`**: older spelling; `dart run build_runner` is the current one in both Dart and Flutter projects. 3. **`--low-resources-mode`**, **`--track-performance`**: removed in 2.15; still accepted with a warning and ignored. ## Behaviour worth knowing - Since build_runner **2.16**, a build **fixes incorrect generated files**: if you hand-edit `person.g.dart` and build again, your edit is overwritten. `--keep-modified-outputs` restores the old behaviour. - Since **2.14**, builders are **AOT-compiled** by default for commands other than `run`, which costs startup time on the first build but speeds up builds afterwards; builders using `dart:mirrors` fall back to JIT. - Only one `build_runner` command runs per package at a time; a new one waits for, or stops, a running `watch`. ## A day with build_runner 1. **After cloning or pulling**: `dart pub get`, then `dart run build_runner build`, so every `.g.dart` and `.freezed.dart` matches the current models. 2. **While editing models**: leave `dart run build_runner watch` running in a terminal. Each save triggers an incremental rebuild of only the affected outputs, usually in a second or two once the builders are compiled. 3. **When switching branches**: `watch` notices the changed files and rebuilds; if results look wrong afterwards, stop it, run `clean`, and build again. 4. **Before pushing**: make sure the generated files match the sources, and let CI verify it. ## What interviewers listen for - That you know generated code is **produced by a separate step**, not by the compiler, so it can be missing or stale. - That you can name the generated file, the `part` line and the generated private helper (`_$PersonFromJson`) and explain how they connect. - That you do not reach for `-d` or `clean` as rituals: `-d` does nothing today, and `clean` is for a confused cache, not for every build.
- Why do tutorials tell you to add -d, and do you still need it?Older build_runner versions refused or prompted when a generated file already on disk had not been produced by the current build, and `--delete-conflicting-outputs` (`-d`) told it to delete such files. Since build_runner 2.7 the flag is ignored and conflicting outputs are always deleted, so it is harmless but unnecessary.
- The IDE shows _$PersonFromJson as undefined right after cloning a repo; what is wrong?Usually nothing but a missing build: the generated part has not been created yet, either because the team does not commit `.g.dart` files or because the model changed. Run `dart run build_runner build`. If it still fails, check that the file declares `part 'person.g.dart';` with the right name.
- When is clean the right tool rather than another build?When the incremental state itself seems wrong: outputs that do not update, errors that persist after the cause is fixed, or after switching between a workspace build and a single-package build. `clean` deletes the cache under `.dart_tool/build`, so the next build redoes everything from sources.
saying these in an interview costs you the question
- Adds build_runner and json_serializable under dependencies instead of dev_dependencies
- Believes -d is still required to overwrite existing generated files
- Expects generated code to exist without ever running build_runner
- Edits a .g.dart file by hand and expects the change to survive
- Runs clean before every build as a ritual, making each build a full one