Skip to content
Operator Overloading
step 1/6

Reading — step 1 of 6

Learn

~3 min readFunctional Patterns

Kotlin lets types overload built-in operators by defining specially-named functions. Used right, this makes domain types feel native; used wrong, it makes code unreadable.

Defining operator functions

Use the operator modifier on functions with the right name and signature:

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
    
    operator fun minus(other: Point): Point {
        return Point(x - other.x, y - other.y)
    }
    
    operator fun times(scalar: Int): Point {
        return Point(x * scalar, y * scalar)
    }
    
    operator fun unaryMinus(): Point {
        return Point(-x, -y)
    }
}

val a = Point(3, 4)
val b = Point(1, 2)
println(a + b)         // Point(x=4, y=6)
println(a - b)         // Point(x=2, y=2)
println(a * 3)         // Point(x=9, y=12)
println(-a)            // Point(x=-3, y=-4)

The compiler maps a + b to a.plus(b), a - b to a.minus(b), etc.

The full operator table

OperatorFunction name
+plus
-minus
*times
/div
%rem
+=plusAssign
-=minusAssign
==equals (special — defined on all types)
<, >, <=, >=compareTo
[i]get(i)
[i] = vset(i, v)
()invoke
..rangeTo
incontains

Indexed access

class Matrix(val rows: Int, val cols: Int) {
    private val data = IntArray(rows * cols)
    
    operator fun get(r: Int, c: Int): Int = data[r * cols + c]
    operator fun set(r: Int, c: Int, value: Int) {
        data[r * cols + c] = value
    }
}

val m = Matrix(3, 3)
m[0, 0] = 5
println(m[0, 0])   // 5

invoke — calling like a function

class Greeter(val greeting: String) {
    operator fun invoke(name: String) = "\$greeting, \$name!"
}

val hello = Greeter("Hello")
println(hello("Alice"))   // "Hello, Alice!"

Comparison via Comparable

For ordering operators, implement Comparable instead of compareTo directly:

data class Version(val major: Int, val minor: Int) : Comparable<Version> {
    override fun compareTo(other: Version): Int {
        return compareValuesBy(this, other, Version::major, Version::minor)
    }
}

Version(1, 5) < Version(1, 6)   // true

When NOT to overload operators

Operator overloading is great when the operator's intuitive meaning matches the operation. It's terrible when it doesn't:

// Good — Vector + Vector is intuitive
operator fun Vector.plus(other: Vector) = Vector(...)

// Bad — User * Permission has no intuitive meaning
operator fun User.times(p: Permission) = grant(this, p)

Readers will assume * does multiplication. Don't surprise them.

Common mistakes

  • Forgetting operator modifier — function works but isn't picked up for the operator syntax.
  • Wrong return typeoperator fun plus should return a meaningful result, often the same type.
  • Surprising semantics== should mean equality, < should mean less than. If it's not intuitive, name it explicitly: Vector.dot() not overloaded *.
  • Implementing equals without hashCode — they go together. Data classes auto-generate both consistently.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…