Skip to content
teach

Lesson 18. Operator Overloading

Mission link: This is stage 3's capstone. Every idiom this stage covered (extension functions, scope functions, higher-order functions, inlining, delegation) is, underneath, an ordinary function; operator overloading is the last piece, showing that even + and [] resolve to ordinary named functions too, and closing the stage's "writes Kotlin a reviewer would not describe as translated Java."
Primary source: Docs: "Operator overloading", Kotlin
Prerequisites: Lesson 17, Platform type

Warm-up

  1. ▢ What does class Derived(b: Base) : Base by b actually generate?
Check

A forwarding method for every member of Base, each one calling the corresponding method on b, generated by the compiler rather than hand-written.

  1. ▢ Why can a delegate's own internal method calls not see a derived class's overrides?
Check

The delegate object only ever sees its own implementations of the interface's members when calling between them internally; delegation is forwarding to a separate object, not extending it, so it has no visibility into whatever the derived class overrode elsewhere.

Know this

Every operator symbol maps to one specific, fixed function name

a + b is exactly a.plus(b); -a is exactly a.unaryMinus(); a[i] is exactly a.get(i); a < b is exactly a.compareTo(b) < 0. Kotlin defines a fixed table mapping each operator symbol to a specific function name and signature; to overload an operator, you implement a member or extension function with that exact name, marked with the operator modifier (operator fun plus(other: Point): Point). This is the direct extension of lesson 13's mechanism (an extension function callable with member syntax) to a fixed, compiler-recognized vocabulary of symbols instead of an arbitrary name you choose yourself.

flowchart LR A["extension functions (13)"] --> F["every idiom this stage covered
is an ordinary function underneath"] B["scope functions (14)"] --> F C["higher-order functions (15)"] --> F D["inline functions (16)"] --> F E["delegation (17)"] --> F F --> G["operators (18):
a + b is a.plus(b)"]

The operator modifier is a compile-time promise, not just a naming convention

Marking a function operator is what actually authorizes the compiler to resolve a + b to it; a function named plus without the operator modifier is just an ordinary function that happens to share the name, never invoked by + syntax. This is a deliberate safety rail: it prevents an accidental function named plus from silently being picked up by operator syntax, and it means every operator overload in a codebase is explicitly, visibly marked as one.

Overloading is safe exactly where the symbol's usual meaning still holds

operator fun Point.unaryMinus() = Point(-x, -y) overloads - to negate a point's coordinates, a use that matches what - already means to any reader: apply the "opposite" operation to this value. This is the good case. Overloading + on a type to do something that has nothing to do with combining or adding two values, say, using it to trigger a side effect or perform an unrelated lookup, produces exactly the "compiles but reads badly" pattern this mission's vocabulary keeps returning to: code that type-checks and runs, but silently violates what a reader (and a reviewer) reasonably assumes + means, since the compiler enforces the signature an operator requires, never the semantics a reader expects from the symbol.

Indexed access, comparisons, and in are operators too, following the same rule

a[i] and a[i] = v map to get(index) and set(index, value), letting a custom type support array-style indexing syntax. a < b, a <= b, and so on all resolve through a single compareTo() function (implementing Comparable), not five separate operator functions. x in collection maps to collection.contains(x). Every one of these follows the same discipline as + and -: implement it only where the operator's usual meaning (ordering, membership, indexed access) genuinely applies to the type, and the operator modifier is what makes the mapping real rather than coincidental naming.

Practice

  1. ▢ What function, with what signature, does a + b actually resolve to?
Check

a.plus(b), an operator fun plus(other: T): R (or an equivalent extension function) declared for a's type, taking one parameter of the type being added and returning the result type.

  1. ▢ A class defines a function named plus with the correct signature but without the operator modifier. Does a + b compile using it?
Hint

Consider what specifically authorizes the compiler to treat a function as backing an operator.

Check

No. Without the operator modifier, plus is just an ordinary function that happens to share the name; the compiler only resolves + to a function explicitly marked operator, which is a deliberate safety rail against an unrelated function silently being picked up by operator syntax.

  1. ▢ Why is operator fun Point.unaryMinus() = Point(-x, -y) a good use of operator overloading, while overloading + on some type to trigger an unrelated side effect is not?
Check

unaryMinus on a point matches what - already means to any reader: producing the "opposite" of a value, here negating both coordinates. Overloading + to do something unrelated to combining two values (a side effect, an unrelated lookup) still compiles and runs correctly by the compiler's rules, but silently violates what a reader reasonably assumes the + symbol means, since the compiler only enforces the required signature, never the reader's expectation of the symbol's meaning.

  1. ▢ How many separate operator functions does implementing <, <=, >, and >= for a custom type actually require, and why?
Check

Just one: all four comparison operators resolve through a single compareTo() function (the Comparable interface's method), not four separate operator implementations. The compiler translates each comparison operator into a check against compareTo()'s return value (negative, zero, or positive).

  1. ▢ Which claim correctly describes operator overloading in Kotlin?

    • a) Any function named plus, get, or compareTo is automatically used for the corresponding operator syntax
    • b) Each operator symbol maps to one specific, fixed function name; the operator modifier is what actually authorizes the compiler to use a given function for that symbol, and overloading is safe specifically where the symbol's conventional meaning still applies to the type
    • c) <, <=, >, and >= each require their own separate operator function implementation
    • d) The compiler enforces that an operator overload's behavior matches the symbol's conventional meaning, rejecting overloads that don't
Check

b) That's the precise mechanism and the precise discipline this lesson (and the stage) closes on. (a) is false: the operator modifier is required; a same-named function without it is never picked up by operator syntax. (c) is false: all four resolve through one shared compareTo() implementation. (d) is false: the compiler only checks that the required signature is met, never whether the implementation's actual behavior matches what a reader would expect from the symbol, which is exactly why misuse is possible and worth actively avoiding.

Real-world reps

  • [ ] Find an operator overload in a codebase you've written or have access to (or in the Kotlin standard library, like arithmetic on a custom numeric-like type). Confirm its behavior genuinely matches what the operator symbol means to a reader, not just that it compiles.
  • [ ] Find a type in your own work that implements Comparable (or would benefit from it). Confirm you understand that a single compareTo() implementation is all four comparison operators need.
  • [ ] Tomorrow: read the primary source's full operator table (unary, binary, indexed access, in, augmented assignment like +=) and identify one operator you didn't know had a fixed function-name mapping.

Going further


Not landing? Reread the primary source at the top, since this lesson compresses it and compression is where understanding leaks. Check the glossary for any term that felt slippery.

If the lesson itself is unclear rather than the material, that is a defect: open an issue.

Table of contents