skip to content

By what mechanism does Kotlin resolve an operator to a function, and what are the constraints and pitfalls (return types, extensions, ambiguity, when NOT to overload)?

level: seniorimportance: nice to knowfreq 30%

answer

  1. Fixed name + operator modifier + normal overload resolution
  2. Members beat extensions; overload by signature allowed
  3. compareTo->Int, contains->Boolean, *Assign->Unit
  4. == uses equals; === not overloadable; precedence fixed
  5. Overload only when the symbol's meaning truly fits

basics

~10 s

Kotlin matches an operator to a fixed function name plus its parameter signature, requiring the operator keyword. Some operators constrain return types. Overloading by signature is allowed, but overusing operators can hurt readability.

solid answer

~40 s

Operator resolution is purely **conventional**: the compiler maps each symbol to a fixed function name (`plus`, `get`, `compareTo`, `contains`, `rangeTo`, `invoke`, `inc`...) and resolves it like any function call using the operand types as arguments, but only if the candidate carries the `operator` modifier. You can **overload by parameter types** (multiple `plus(Int)` / `plus(Vec)`), and operators can be **member or `operator` extension** functions — enabling operators on third-party types; member functions win over extensions on ambiguity. Return-type rules differ: `compareTo` -> `Int`, `contains` -> `Boolean`, `*Assign` -> `Unit`, while arithmetic operators are unconstrained. Pitfalls: the `plus`/`plusAssign` ambiguity, accidentally surprising semantics (e.g. non-commutative `+`), and operators that read worse than a named method. Best practice: overload only when the symbol's conventional meaning genuinely fits the type (math-like values, ranges, containers, DSLs).

code

kotlin · 5 lines
kotlin
// Operator on a type you don't own, via extension; overloaded by signature
operator fun String.get(range: IntRange) = substring(range)

val s = "kotlin"
println(s[0..2])   // String already has get(Int); this adds get(IntRange) -> "kot"

go deeper

for a junior

Knows the operator maps to a named function and needs the keyword.

for a middle

Explains overload-by-signature and member-vs-extension, and a couple of return-type rules.

for a senior

Articulates the full resolution model, return-type constraints, the plus/plusAssign and ==/equals gotchas, and tasteful 'when not to overload' judgment.

for a principal

Defines team-wide guidance on operator semantics (commutativity, no hidden side effects, value-vs-mutable receivers) and reviews APIs for misleading overloads.

## How resolution actually works Kotlin has **no user-defined operator symbols**. Instead, each operator is hardwired to a **conventional function name**, and the compiler does an ordinary **overload resolution** on that name using the operands as arguments — gated by the **`operator`** modifier. ``` expression convention a + b a.plus(b) a[i] a.get(i) a[i] = v a.set(i, v) a() a.invoke() a < b a.compareTo(b) < 0 a in c c.contains(a) a..b a.rangeTo(b) a++ a.inc() ``` Because it's normal overload resolution, the usual rules apply: most specific signature wins, members are preferred over extensions, and you can have **several overloads** of the same operator distinguished by parameter types. ```kotlin class Vec(val x: Int, val y: Int) { operator fun plus(o: Vec) = Vec(x + o.x, y + o.y) operator fun plus(s: Int) = Vec(x + s, y + s) // overload by signature } ``` ## Member vs extension Operators can be declared as **`operator` extension functions**, which is the only way to add operator syntax to types you don't own: ```kotlin operator fun Int.times(s: String) = s.repeat(this) val line = 3 * "ab" // "ababab" ``` If both a member and an extension match, the **member wins**. ## Return-type constraints (a frequent gotcha) Most operators let the return type be anything, but several are constrained: - `compareTo` **must return `Int`**. - `contains` **must return `Boolean`**. - `plusAssign`/`minusAssign`/... should return `Unit`. - `inc`/`dec` must return a type **assignable to the receiver's variable**. - `rangeTo` is unconstrained but should return something with a `contains` to be useful with `in`. Violating these is a compile error (e.g. `compareTo` returning `Boolean`). ## Pitfalls and ambiguities - **`plus` vs `plusAssign`:** defining both for a `var` makes `a += b` ambiguous (compile error). - **Surprising semantics:** overloading `+` to do something non-additive (e.g. side effects, non-commutative behavior where readers expect commutativity) misleads readers. Operators should obey the intuitive algebra of the symbol. - **`==`/`equals` confusion:** `==` is not an operator function; override `equals` (no `operator` modifier). `===` cannot be overloaded. - **Precedence is fixed:** you can't make your `+` bind tighter than `*`. ## When NOT to overload Overload operators only when the symbol's conventional meaning clearly maps to the type: - **Good:** vectors/matrices/complex numbers (`+`, `*`), money (`+`, `-`), durations, ranges (`..`, `in`), containers (`[]`, `in`, `+=`), DSL/factory objects (`invoke`). - **Avoid:** using `+` for unrelated operations, or replacing a clearly named method (`user.grantRole(r)`) with an obscure operator just to look terse. A named function is better when the meaning isn't universally obvious. ## Summary Resolution = fixed name + `operator` modifier + ordinary overload resolution on operand types. Respect the return-type constraints, avoid the plus/plusAssign clash, and reserve overloading for cases where the operator's standard meaning genuinely fits — otherwise prefer a descriptive named function.

  • If a member operator and an extension operator both match, which is chosen?
    The member function takes precedence over the extension.
  • Can you overload the same operator multiple times on one type?
    Yes — define multiple `operator fun plus(...)` overloads differing by parameter types; normal overload resolution selects one.
  • Why is overloading `+` to mutate state usually a bad idea?
    `+` reads as producing a new value; hidden mutation or side effects violate reader expectations and the symbol's conventional algebra.

It's like a fixed dictionary of symbols: you can supply your own definition for each existing word, but you can't add new words or change the grammar's word order.

saying these in an interview costs you the question

  • Claiming you can define new operator symbols or change precedence
  • Thinking extensions override members on ambiguity
  • Ignoring return-type constraints (e.g. compareTo returning Boolean)
  • Overloading operators with surprising, non-conventional semantics
  • Confusing `==`/`equals` with an operator function

context