Skip to content

avoid_collection_equality_checks

v0.3.0 Warning Collection & Type

Flags == or != where either side is a List, Set, Map or Iterable. Collections have no deep equality in Dart: two distinct instances are never equal, however identical their contents.

The comparison compiles and always answers false, so the branch it guards never runs:

bool hasSelectionChanged(List<String> previous, List<String> current) {
return previous != current;
}

Here hasSelectionChanged returns true for every call — including when nothing changed — because previous and current are separate objects.

Compare contents with DeepCollectionEquality from package:collection:

import 'package:collection/collection.dart';
bool hasSelectionChanged(List<String> previous, List<String> current) {
return !const DeepCollectionEquality().equals(previous, current);
}

For a flat list, ListEquality is cheaper and states the depth you expect:

import 'package:collection/collection.dart';
bool hasSelectionChanged(List<String> previous, List<String> current) {
return !const ListEquality<String>().equals(previous, current);
}

The same trap sits in a hand-written ==, where it silently makes every instance unequal:

// Don't
class Cart {
const Cart(this.items);
final List<String> items;
@override
bool operator ==(Object other) => other is Cart && items == other.items;
}
// Do
import 'package:collection/collection.dart';
class Cart {
const Cart(this.items);
final List<String> items;
@override
bool operator ==(Object other) =>
other is Cart && const ListEquality<String>().equals(items, other.items);
@override
int get hashCode => const ListEquality<String>().hash(items);
}

Three shapes are deliberately accepted:

  • A null check. items == null and items != null are the normal way to test presence and are never reported.
  • Two compile-time constants. const [1, 2] == const [1, 2] is true, because constant collections are canonicalized to one instance. Both sides must be const for the exemption to apply.
  • Neither side a collection. At least one operand must be a List, Set, Map or Iterable, so ordinary value comparisons are untouched.

See also: collection package

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

To turn it off:

many_lints.yaml
rules:
avoid_collection_equality_checks: false

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