What is the difference between == and identical()?

Medium PriorityAsked in ~45% of Flutter interviews

3 min read

Dart Fundamentals

==identical(a, b)
ChecksEquality (semantics)Identity (same memory slot)
Overridable✅ Yes (operator)❌ Never
Default behaviourSame as identicalPointer comparison
When to overrideAlmost always for value classesn/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 pathidentical(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

  1. Why must hashCode change when == changes? Set and Map first 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.

  2. 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).

  3. Why do two const objects with the same fields satisfy identical? The compiler canonicalises const instances — it emits a single shared instance per (constructor, arguments) pair. Flutter uses this to skip rebuilding const widgets.

  4. How does Dart 3's records change the equality story? Records have built-in structural == and hashCode. (1, 2) == (1, 2) is true out 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.