Skip to content

prefer_early_return

v1.0.0WarningFixConfigurableControl Flow

This rule flags a function body that consists of a single if wrapping everything the function does, where inverting the condition and returning early would remove a level of indentation.

The reader has to hold “we are inside the valid case” for the entire body, and every further condition nests one level deeper. A guard states the precondition once and lets the rest of the function be the happy path.

The whole function body is one if, and the useful work is a level in:

class Order {
Order({required this.isPaid});
final bool isPaid;
void reserveStock() {}
void printLabel() {}
void dispatch() {}
void notifyCustomer() {}
}
void ship(Order order) {
if (order.isPaid) {
order.reserveStock();
order.printLabel();
order.dispatch();
order.notifyCustomer();
}
}
void ship(Order order) {
if (!order.isPaid) return;
order.reserveStock();
order.printLabel();
order.dispatch();
order.notifyCustomer();
}

The rule is deliberately narrow. It stays silent in each of these:

A statement before the if. That is setup the guard form would have to move or duplicate:

void ship(Order order) {
final startedAt = DateTime.now();
if (order.isPaid) {
order.reserveStock();
order.printLabel();
order.dispatch();
}
}

An else branch. Inverting would swap the branches rather than flatten anything — that case belongs to avoid_negated_conditions.

An already-negated condition. if (!map.containsKey(key)) inverts into a longer positive guard, and the negation is what made the precondition obvious:

void register(Map<String, int> counts, String key) {
if (!counts.containsKey(key)) {
counts[key] = 0;
counts[key] = counts[key]! + 1;
print('registered $key');
}
}

A pattern if. if (x case final int n) binds variables the inverted branch cannot see.

A body shorter than min_statements — three by default. See below.

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

many_lints.yaml
rules:
prefer_early_return:
enabled: true

How many statements the wrapped block must hold before the guard is worth it. Below that, the rewrite saves no nesting worth doing:

analysis_options.yaml
many_lints:
rules:
prefer_early_return:
min_statements: 4

With min_statements: 4, a three-statement body stops being reported:

void ship(Order order) {
// Not reported at min_statements: 4 — reported at the default of 3.
if (order.isPaid) {
order.reserveStock();
order.printLabel();
order.dispatch();
}
}
Option Type Default Description
min_statements int 3 How many statements the wrapped block must hold before the guard is worth it

To disable this rule:

many_lints.yaml
rules:
prefer_early_return: false

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