avoid_high_cyclomatic_complexity
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); }}More examples
Section titled “More examples”An exhaustive switch counts as one
Section titled “An exhaustive switch counts as one”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.
Tightening the budget
Section titled “Tightening the budget”Ten is the threshold most complexity tools have converged on. Drop it if you want functions split sooner:
rules: avoid_high_cyclomatic_complexity: max_complexity: 6Enabling this rule
Section titled “Enabling this rule”This rule is in the pedantic preset, so it is enabled by preset: pedantic or by name:
rules: avoid_high_cyclomatic_complexity: enabled: trueOptions
Section titled “Options”many_lints: rules: avoid_high_cyclomatic_complexity: max_complexity: 6 count_exhaustive_switches: falserules: 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 |
Turning this rule off
Section titled “Turning this rule off”To disable this rule:
rules: avoid_high_cyclomatic_complexity: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_complex_conditions— Keep boolean conditions within an operand budget.avoid_deep_nesting— Keep control flow within a nesting budget.avoid_long_functions— Keep function bodies within a line budget.max_statements— Keep a function within a statement budget.