What is the difference between == and identical()?
3 min read
Dart Fundamentals
== | identical(a, b) | |
|---|---|---|
| Checks | Equality (semantics) | Identity (same memory slot) |
| Overridable | ✅ Yes (operator) | ❌ Never |
| Default behaviour | Same as identical | Pointer comparison |
| When to override | Almost always for value classes | n/a |
Rule: if you override ==, you must override hashCode — otherwise your objects break in Set / Map.
Code in action
// Default class — == falls back to identity
class Point { final int x, y; Point(this.x, this.y); }
final p1 = Point(1, 2);
final p2 = Point(1, 2);
p1 == p2; // false — different instances
identical(p1, p2); // false
// Override == for value equality
class Point {
final int x, y;
const Point(this.x, this.y);
@override
bool operator ==(Object o) =>
identical(this, o) || (o is Point && o.x == x && o.y == y);
@override
int get hashCode => Object.hash(x, y); // ALWAYS pair them
}
final a = Point(1, 2), b = Point(1, 2);
a == b; // true — same values
identical(a, b); // false — still two objects
// const canonicalises — same value literal = same object
const c = Point(1, 2);
const d = Point(1, 2);
c == d; // true
identical(c, d); // true ← only because of `const`
Why it matters in Flutter
// const widgets are canonicalised → Flutter can skip rebuilds
const Padding(padding: EdgeInsets.all(8), child: Text('Hi'));
const Padding(padding: EdgeInsets.all(8), child: Text('Hi'));
// these two are identical(), so the framework can short-circuit.
// And the canonical == fast-path pattern:
@override
bool operator ==(Object other) {
if (identical(this, other)) return true; // O(1) pointer check first
return other is Foo && /* deeper equality */;
}
When to reach for which
| You want to… | Use |
|---|---|
| Compare values of a data class (User, Address, Point) | == (overridden) |
| Detect "is this the same object I had before?" (cache, identity map) | identical() |
Speed up your own == with a fast path | identical(this, other) first |
Compare primitives (int, String, bool) | == — already value-equal |
Common mistakes to avoid
// ❌ Overriding == without hashCode
class User {
final String id;
User(this.id);
@override
bool operator ==(Object o) => o is User && o.id == id;
// hashCode is still Object's identity hash → Sets/Maps misbehave
}
// ✅ Always pair them
@override
int get hashCode => id.hashCode;
// ❌ Using == on collections expecting deep equality
[1, 2, 3] == [1, 2, 3]; // false — Lists use identity
// ✅ Use collection-aware helpers
const ListEquality().equals([1, 2, 3], [1, 2, 3]); // true
// ❌ Relying on identical() for "value" classes
identical(User('a'), User('a')); // false — almost always
// ❌ Forgetting that non-const constructors don't canonicalise
final a = Point(1, 2);
final b = Point(1, 2);
identical(a, b); // false even if == is overridden
Interview follow-ups
-
Why must
hashCodechange when==changes?SetandMapfirst hash the object to find the right bucket, then use==to compare. If equal objects produce different hashes, they end up in different buckets —set.contains(equalObj)returns false. Breaking this contract silently corrupts data structures. -
What's the fast path inside a custom
==?if (identical(this, other)) return true;— pointer equality is essentially free, and dodging the deep comparison pays off in hot code (widget diffing, list equality). -
Why do two
constobjects with the same fields satisfyidentical? The compiler canonicalises const instances — it emits a single shared instance per (constructor, arguments) pair. Flutter uses this to skip rebuildingconstwidgets. -
How does Dart 3's records change the equality story? Records have built-in structural
==andhashCode.(1, 2) == (1, 2)istrueout of the box — no boilerplate needed. Great for keys and quick value tuples.
How helpful was this content?
Please sign in to rate this article.