Skip to content

avoid_high_cyclomatic_complexity

v1.0.0WarningConfigurableCode Quality

This rule flags a function with more independent paths through it than the configured budget — every branch, loop, catch, &&, ||, ?: and ?? adds one.

It measures what the line and statement budgets miss: twenty sequential statements are easy, six nested conditions in five lines are not.

This rule is in the pedantic preset. The default budget is max_complexity: 10.

This describe has eleven paths — five ifs (two of them with a second operand), a loop with a branch inside it, a ?? and a catch:

String describe(int n, bool flag, String? label) {
if (n < 0) return 'negative';
if (n == 0) return 'zero';
if (n > 100 && flag) return 'large and flagged';
if (n > 100 || flag) return 'large or flagged';
if (label != null && label.isNotEmpty) return label;
for (var i = 0; i < n; i++) {
if (i.isEven) print(i);
}
try {
return int.parse(label ?? '').toString();
} on FormatException {
return 'bad format';
}
}

Split the decisions across functions. Every branch still exists — each one now lives somewhere small enough to test on its own:

String describe(int n, bool flag, String? label) {
final size = _size(n, flag);
if (size != null) return size;
return _fromLabel(n, label);
}
String? _size(int n, bool flag) {
if (n < 0) return 'negative';
if (n == 0) return 'zero';
if (n > 100 && flag) return 'large and flagged';
if (n > 100 || flag) return 'large or flagged';
return null;
}
String _fromLabel(int n, String? label) {
if (label != null && label.isNotEmpty) return label;
_printEvensBelow(n);
try {
return int.parse(label ?? '').toString();
} on FormatException {
return 'bad format';
}
}
void _printEvensBelow(int n) {
for (var i = 0; i < n; i++) {
if (i.isEven) print(i);
}
}

The compiler proves every case is handled, so the cases are not paths the reader has to verify. Counting them would report exactly the exhaustive pattern matching Dart 3 encourages:

enum Stage { queued, running, retrying, failed, done, cancelled }
// Not reported, however many cases it grows to.
String label(Stage stage) => switch (stage) {
Stage.queued => 'Queued',
Stage.running => 'Running',
Stage.retrying => 'Retrying',
Stage.failed => 'Failed',
Stage.done => 'Done',
Stage.cancelled => 'Cancelled',
};

Exhaustibility is read from the switched value’s static type, not from the patterns — an enum or a sealed class is exhaustible, anything else is not. A switch over a String or an int therefore still counts case by case. To count every switch that way, set count_exhaustive_switches: true.

operator == and copyWith are never reported

Section titled “operator == and copyWith are never reported”

Both grow with the field count rather than with any decision, and neither can be split:

class Booking {
const Booking(this.id, this.court, this.start, this.end, this.notes);
final String id;
final int court;
final DateTime start;
final DateTime end;
final String? notes;
// Not reported — one `&&` per field by construction.
@override
bool operator ==(Object other) =>
other is Booking &&
other.id == id &&
other.court == court &&
other.start == start &&
other.end == end &&
other.notes == notes;
@override
int get hashCode => Object.hash(id, court, start, end, notes);
// Not reported — one `??` per parameter.
Booking copyWith({String? id, int? court, DateTime? start, DateTime? end}) =>
Booking(
id ?? this.id,
court ?? this.court,
start ?? this.start,
end ?? this.end,
notes,
);
}

The exemption is by member name, so it covers exactly == and copyWith. A validating constructor still reports: its checks are genuine independent decisions.

Ten is the threshold most complexity tools have converged on. Drop it if you want functions split sooner:

many_lints.yaml
rules:
avoid_high_cyclomatic_complexity:
max_complexity: 6

This rule is in the pedantic preset, so it is enabled by preset: pedantic or by name:

many_lints.yaml
rules:
avoid_high_cyclomatic_complexity:
enabled: true
analysis_options.yaml
many_lints:
rules:
avoid_high_cyclomatic_complexity:
max_complexity: 6
count_exhaustive_switches: false
Option Type Default Description
max_complexity int 10 How many independent paths a function may have
count_exhaustive_switches bool false Whether each case of an exhaustive switch counts separately

To disable this rule:

many_lints.yaml
rules:
avoid_high_cyclomatic_complexity: false

To keep the rule on but skip certain paths, use per-rule exclude.