skip to content

In Dart, how does the identical() function differ from ==, and when do you need identical() or an identity-based Set or Map?

level: middleimportance: should knowfreq 40%

answer

  1. method call versus top-level function
  2. overridable versus fixed
  3. identityHashCode ignores overrides
  4. Set.identity and Map.identity
  5. equal ints are identical

basics

~20 s

== calls the left operand's overridable operator ==; identical(a, b) is a top-level function that only tests whether both references are the same object. Use identity for fast paths, visited-sets and Set.identity or Map.identity collections.

solid answer

~40 s

`a == b` is a method call on `a` after Dart handles the `null` cases, so its meaning is whatever the class defines: identity by default, value equality after an override. `identical(a, b)` from `dart:core` always asks "is this the same object?" and no class can change its answer. You need identity when value equality is the wrong question: tracking which objects a traversal has already visited, detecting cycles, caching per instance, or keeping two field-equal but distinct objects apart. For that, use `Set.identity()`, `Map.identity()` or `HashMap.identity()`, which pair `identical` with `identityHashCode`; the latter returns the original `Object.hashCode` even when the class overrides `hashCode`. Numbers are special: two equal `int` values are always identical, and constant canonicalization makes equal `const` objects identical.

code

dart · 27 lines
dart
class Coordinate {
  const Coordinate(this.lat, this.lng);
  final double lat;
  final double lng;

  @override
  bool operator ==(Object other) =>
      other is Coordinate && other.lat == lat && other.lng == lng;

  @override
  int get hashCode => Object.hash(lat, lng);
}

void main() {
  final a = Coordinate(52.52, 13.405);
  final b = Coordinate(52.52, 13.405);

  print(a == b); // true: overridden ==
  print(identical(a, b)); // false: two objects

  final byValue = <Coordinate>{a, b};
  final byIdentity = Set<Coordinate>.identity()..addAll([a, b]);
  print(byValue.length); // 1
  print(byIdentity.length); // 2

  print(identical(const Coordinate(1, 2), const Coordinate(1, 2))); // true
}

go deeper

for a junior

Recall that == can be customised per class while identical() always tests for the same object.

for a middle

Explain how null is handled before operator == runs, why identityHashCode ignores overrides, and which collections use identity.

for a senior

Show where identity is the right question in real code, such as traversal visited-sets and per-instance bookkeeping, and the bugs value equality causes there.

for a principal

Decide where a codebase relies on identity versus value equality, for example immutable data with cheap identity change checks, and make that choice explicit in APIs.

## Two different questions Dart gives you two ways to ask whether two references match: - **`a == b`** asks "are these *equal*?" It is an operator that invokes `operator ==` on the left operand, so the answer depends on the class. `Object`'s default is identity; `String`, `int`, records and your own value classes can override it to compare values. - **`identical(a, b)`** asks "are these the *same object*?" It is a top-level function in `dart:core`, not a method, so **no class can override it**. For `a == b`, the language also handles `null` before any method runs: if either operand is `null`, the result is `true` only when both are, and `operator ==` is called only with a non-null argument. ## How they diverge | Expression | Default class | Class overriding `==` by fields | |---|---|---| | `a == a` | `true` | `true` | | two field-equal instances with `==` | `false` | `true` | | two field-equal instances with `identical` | `false` | `false` | | `a.hashCode` | identity hash | value hash | | `identityHashCode(a)` | identity hash | identity hash (override ignored) | `identityHashCode(object)` returns the value the original `Object.hashCode` would return, **even if `hashCode` has been overridden**. That is what makes identity collections possible for value classes. ## Special cases worth knowing - **Integers**: Dart does not create distinct `int` instances with the same value, so two equal integers are always identical; `identical(2, 1 + 1)` is `true`. - **Doubles**: equal binary representations are identical, with documented caveats for `NaN` and `-0.0` on web platforms. - **Constants**: constant canonicalization makes two constant expressions with the same state evaluate to one instance, so `identical(const Coordinate(1, 2), const Coordinate(1, 2))` is `true` for a class with a `const` constructor. - **New objects**: a non-constant call to a generative constructor, or a non-constant list, set or map literal, always creates a new object that is identical to nothing else. ## When identity is the right tool 1. **A fast path inside `==`.** `identical(this, other) || ...` returns early when an object is compared with itself, which helps when there are many fields. 2. **Graph traversal and cycle detection.** When walking a structure of value objects, "have I visited *this node*?" is an identity question. With a value-based `Set`, two distinct nodes that happen to hold equal data would be merged and one would be skipped. 3. **Per-instance bookkeeping.** Attaching metadata to specific objects, such as which listener objects are registered, needs identity keys so that two equal-looking listeners stay separate. 4. **Change detection on immutable data.** If a value is immutable, "same object as before" is a cheap proof that nothing changed; a full `==` may walk many fields or collections. ## Identity collections `dart:core` and `dart:collection` provide identity-based variants: - `Set.identity()` creates a `LinkedHashSet` using `identical` and `identityHashCode`. - `Map.identity()` creates a `LinkedHashMap` keyed by identity. - `HashMap.identity()` and `LinkedHashSet.identity()` offer the same in `dart:collection`. With these, two field-equal coordinates are two entries even though `==` says they are equal. You can also build a custom one with `HashSet(equals: identical, hashCode: identityHashCode)`, the example the `identityHashCode` documentation itself uses with two equal `DateTime` values. ## Cost `identical` is a reference comparison with a fixed, tiny cost. `==` costs whatever the class makes it cost: comparing two fields is cheap, but a class whose `==` walks a list field with `listEquals` is linear in that list's length, and a deep comparison is worse. The same applies to `hashCode`, which is recomputed on every lookup unless the class caches it. That asymmetry is why immutable data pairs so well with identity checks: once an object cannot change, "same object" is a sufficient proof of "same value", and it is the cheapest one available. ## Common mistakes - Using `identical` to compare values such as strings read from input: equal strings are not guaranteed to be the same object. - Assuming `identical` falls back to `==` or to `hashCode`: it does neither. - Using `==` in a traversal's visited set for a value class, and silently skipping distinct nodes.

  • Why is identical(this, other) often the first check inside an operator == override?
    An object is always equal to itself, so when both references are the same object the method can return `true` without comparing fields. It is a cheap shortcut that pays off with many fields or collection fields. It is optional, and the Dart docs note it is not automatic because floating-point `NaN` is not equal to itself.
  • Can two separately written string literals with the same text be identical in Dart?
    Constant strings are canonicalized, so two identical string literals typically are the same object, but strings built at runtime, such as those from concatenation or decoding, are not guaranteed to be. Always compare strings with `==`; `identical` answers a question about memory, not text.

saying these in an interview costs you the question

  • identical() calls operator == when the class overrides it.
  • identical() is true whenever two objects have the same hashCode.
  • A class can override identical() to customise identity.
  • identityHashCode returns the overridden hashCode when there is one.
  • Two equal int values in Dart can be different objects.