skip to content

In Kotlin, what is the difference between == and ===? Which one calls equals()?

level: juniorimportance: must knowfreq 85%

answer

  1. == is structural -> calls equals()
  2. === is referential -> same instance
  3. == compiles to a?.equals(b) ?: (b === null), null-safe
  4. != and !== negate them
  5. data class makes == compare contents

basics

~10 s

== checks if two values are equal (same contents) by calling equals(). === checks if two variables point to the exact same object in memory. Use == for value checks.

solid answer

~40 s

In Kotlin == is structural equality: a == b compiles to a?.equals(b) ?: (b === null), so it calls equals() and is null-safe — no NullPointerException even when a is null. === is referential (identity) equality: it returns true only when both references point to the same object instance, never calling equals(). Their negations are != (structural) and !== (referential). For data classes, equals() is auto-generated from the properties in the primary constructor, so == compares contents. For most app code you want ==; reach for === only when identity itself matters (cache/interning checks, cycle detection). Unlike Java, Kotlin has no separate .equals() syntax requirement for == — the operator already routes to equals().

code

kotlin · 8 lines
kotlin
val a: String? = null
val b: String? = null
println(a == b) // true, null-safe, no NPE

val x = StringBuilder("hi").toString()
val y = StringBuilder("hi").toString()
println(x == y)  // true  (same chars)
println(x === y) // false (different instances)

go deeper

for a junior

States == is structural (calls equals) and === is referential (same object); picks == for value comparison.

for a middle

Recalls the desugaring a?.equals(b) ?: (b === null) and explains null-safety; mentions data class equals generation.

for a senior

Discusses when identity (===) is legitimately needed and string interning caveats; ties to hashCode contract.

for a principal

Frames equality choice as an API design concern across the codebase and warns against === leaking implementation/identity assumptions into business logic.

## Two kinds of equality Kotlin distinguishes **structural equality** (do these represent the same value?) from **referential equality** (are these the same object in memory?). - **`==` (structural)** — the equality operator. The compiler translates `a == b` into: ```kotlin a?.equals(b) ?: (b === null) ``` So it calls the `equals()` method, and it is **null-safe**: if `a` is `null`, no method is invoked and the result is simply `b === null` (true only if `b` is also null). This is why `==` never throws a `NullPointerException` in Kotlin, unlike calling `a.equals(b)` directly in Java. - **`===` (referential / identity)** — returns `true` only when the two operands point to the **exact same object instance**. It never calls `equals()`. For primitive-backed types that map to JVM primitives (e.g. `Int`), `===` compares values directly. ## Negations - `!=` negates `==` (structural). - `!==` negates `===` (referential). ## equals() and data classes `equals()` comes from `Any`. By default (a plain class) it behaves like identity, so `==` and `===` agree. A **`data class`** auto-generates `equals()`/`hashCode()` from the properties declared in the **primary constructor**, so `==` compares contents: ```kotlin data class Point(val x: Int, val y: Int) val a = Point(1, 2) val b = Point(1, 2) println(a == b) // true -> structural, contents match println(a === b) // false -> different instances ``` ## When to use which - Use **`==`** for almost everything: comparing values, strings, list contents, etc. - Use **`===`** only when identity matters: checking interning/caching, detecting that an object reference was reused, or breaking reference cycles. ## Gotcha String literals may be interned so `===` can surprisingly return `true`, but you must never rely on that — always compare strings with `==`.

  • Does a == b throw if a is null?
    No. == compiles to a?.equals(b) ?: (b === null), so a null left operand is handled safely and returns true only when b is also null.
  • What does == do for a plain (non-data) class with no overridden equals()?
    It falls back to Any.equals(), which is identity-based, so == behaves like === for that class.

== asks 'are these two banknotes worth the same amount?'; === asks 'is this the very same physical banknote?'

saying these in an interview costs you the question

  • Saying == compares references like Java's == on objects
  • Claiming == can throw NullPointerException
  • Thinking === calls equals()
  • Relying on === to compare String contents
  • Believing != and !== are the same operator

context