Skip to content

function_always_returns_null

v1.0.0 Warning Code Quality

This rule flags a function declared with a nullable return type whose every return yields null. No caller can ever receive a value, yet every caller is forced to null-check.

A function typed String? promises that a String is sometimes possible. When every path returns null, that promise is false: the nullable type is pure overhead, spreading null checks through the callers for a value that never arrives.

In practice this is almost always a leftover — an unfinished implementation, or a refactor that removed the real return and left the signature behind. Either way the declaration and the body disagree, and the body is the one telling the truth.

See also: Dart: understanding null safety

A lookup whose real return was removed in a refactor and never put back:

class SessionStore {
final Map<String, String> _tokens = {};
String? tokenFor(String userId) {
if (userId.isEmpty) return null;
if (!_tokens.containsKey(userId)) return null;
return null; // the read was lost; every caller null-checks for nothing
}
}

Return the value the signature promises:

class SessionStore {
final Map<String, String> _tokens = {};
String? tokenFor(String userId) {
if (userId.isEmpty) return null;
return _tokens[userId];
}
}

Or, if the function genuinely produces nothing, say so in the type:

class SessionStore {
final Map<String, String> _tokens = {};
void forget(String userId) {
_tokens.remove(userId);
}
}

In a nullable-returning function return; yields null, so this reports too — the mix of styles is often what hid the problem:

class Draft {
String? _title;
String? titleOrNull() {
if (_title == null) return;
return null;
}
}

@override methods are skipped. An override must keep the inherited signature, so the author may have no freedom to change it.

async and generator bodies are skipped. Their declared type wraps the value — Future<String?> — so “every return is null” does not carry the same meaning, and a Future<String?> returning null is a normal “not found”.

The return type must be written out. An omitted annotation is inferred as Null, which the analyzer surfaces on its own terms, so this rule only reads types the author typed.

A function with no return at all is not reported. The analyzer already covers that with body_might_complete_normally_nullable.

Returns inside a nested closure belong to that closure, not to the enclosing function.

This rule is in the recommended preset, so it is on with preset: recommended or preset: opinionated.

To turn it off:

many_lints.yaml
rules:
function_always_returns_null: false

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