skip to content

How would you make Kotlin's +, *, and < work on a custom Vector2 class, and what rules govern those operator functions?

level: seniorimportance: nice to knowfreq 30%

answer

  1. operator keyword + fixed name: plus/minus/times/div/rem
  2. All four ordering ops share compareTo -> Int
  3. += prefers plusAssign, else a = a.plus(b)
  4. Can't invent symbols or change precedence
  5. Members or extensions; commutativity isn't free

basics

~10 s

Write functions with special names marked with the operator keyword: plus for +, times for *, and compareTo for <. Kotlin then lets callers use the symbols directly on your type.

solid answer

~40 s

Kotlin operator overloading is convention-based: you declare member or extension functions with **fixed names** and the `operator` modifier. `+` maps to `plus`, `-` to `minus`, `*` to `times`, `/` to `div`, `%` to `rem`. The ordering operators `<`, `>`, `<=`, `>=` all map to a single `compareTo(other): Int` (negative/zero/positive). Compound assignments like `+=` use `plusAssign` if present, otherwise fall back to `a = a.plus(b)`. The function must have the exact name, the `operator` keyword, and the right arity/return shape (compareTo must return Int). You cannot invent new symbols or change precedence/associativity. For data-carrying value types, returning a new instance (immutability) is idiomatic; mutating in place is reserved for the *Assign variants.

code

kotlin · 8 lines
kotlin
data class Complex(val re: Double, val im: Double) {
    operator fun plus(o: Complex) = Complex(re + o.re, im + o.im)
    operator fun times(o: Complex) =
        Complex(re * o.re - im * o.im, re * o.im + im * o.re)
    operator fun unaryMinus() = Complex(-re, -im)
}
// commutative scalar multiply needs an extension on Double:
operator fun Double.times(c: Complex) = Complex(this * c.re, this * c.im)

go deeper

for a junior

Knows operators map to specially named functions you can implement.

for a middle

Names the plus/times/compareTo conventions and uses the operator keyword correctly.

for a senior

Explains member-vs-extension, commutativity needing reverse operators, and the +=/plusAssign resolution including the ambiguity error.

for a principal

Advises on when overloading aids vs harms readability, immutability of value types, total-order discipline for compareTo, and API design across owned/unowned types.

## Convention-based operator overloading Kotlin maps operator symbols to functions with **predefined names** plus the `operator` modifier. You implement the function; the compiler wires the symbol to it. | Symbol | Function name | |--------|---------------| | `a + b` | `a.plus(b)` | | `a - b` | `a.minus(b)` | | `a * b` | `a.times(b)` | | `a / b` | `a.div(b)` | | `a % b` | `a.rem(b)` | | `-a` | `a.unaryMinus()` | | `a < b` etc. | `a.compareTo(b)` (one function for all four) | | `a += b` | `a.plusAssign(b)` or `a = a.plus(b)` | ### Example ```kotlin data class Vector2(val x: Double, val y: Double) : Comparable<Vector2> { operator fun plus(o: Vector2) = Vector2(x + o.x, y + o.y) operator fun times(k: Double) = Vector2(x * k, y * k) private fun length() = kotlin.math.hypot(x, y) override fun compareTo(other: Vector2) = length().compareTo(other.length()) } val a = Vector2(1.0, 2.0) val b = Vector2(3.0, 4.0) a + b // Vector2(4.0, 6.0) a * 2.0 // Vector2(2.0, 4.0) a < b // a.compareTo(b) < 0 -> true ``` ## Rules and constraints - The function **must** be named exactly as the convention dictates and carry the `operator` keyword, or the symbol won't compile against it. - You **cannot** define new operators or change **precedence/associativity** — those are fixed by the grammar. - `compareTo` must return `Int`; implementing it (directly or via `Comparable`) enables all four ordering operators at once. - Operators can be **members or extension functions**, so you can add `+` to types you don't own. - Commutativity is not automatic: `2.0 * vector` needs a separate `operator fun Double.times(v: Vector2)` extension; defining `vector * 2.0` does not give you `2.0 * vector`. ## Compound assignment subtlety For `a += b`, Kotlin first looks for `plusAssign` (returns `Unit`, mutates in place). If absent, it rewrites to `a = a.plus(b)` — which requires `a` to be a `var`. Defining **both** `plus` and `plusAssign` on a mutable type is a compile error for `+=` because the call becomes ambiguous. ## Idiomatic guidance - For immutable value types (vectors, money, complex numbers), return **new** instances from `plus`/`times`. - Only overload when the symbol's meaning is **obvious** and matches arithmetic intuition; abusing `+` for unrelated semantics hurts readability. - Implement `compareTo` only when a meaningful **total order** exists.

  • Why can defining both plus and plusAssign cause a += compile error?
    For a += b Kotlin can resolve it as either plusAssign(b) or a = a.plus(b). With both available the call is ambiguous, so the compiler rejects it.
  • Does overloading + on vectors automatically make subtraction work?
    No. Each operator maps to its own function; you must separately define minus for -, times for *, etc. Only the four ordering operators share compareTo.

It's like adapter plugs: Kotlin's sockets (+, *, <) are fixed shapes, and you just supply correctly-named adapters (plus, times, compareTo) so your type fits the existing sockets.

saying these in an interview costs you the question

  • Thinking you can invent brand-new operator symbols
  • Forgetting the operator keyword
  • Expecting commutativity (2.0 * v) without a reverse extension
  • Returning the wrong type from compareTo (not Int)
  • Believing one overload changes operator precedence

context