Kotlin lets a custom type participate in familiar operators by implementing specially named functions marked operator. A plus function can support a + b, get can support indexed access, and contains can support in. The symbols keep their language-defined precedence; your code supplies behavior for the supported convention.
The numbers and operators lesson teaches ordinary arithmetic. This reference asks a different question: when does symbolic syntax make a domain type easier to understand? You will implement a small immutable score and a bounded collection lookup while learning why an operator should preserve the expectations readers already bring to its symbol.
Review classes and objects before implementing the custom types below.
Operator names form a language contract
You cannot invent a new symbol with arbitrary precedence. Kotlin recognizes a predefined set of names such as plus, minus, times, get, set, contains, invoke and compareTo. Mark an applicable member or extension function operator and give it the required signature. The compiler translates the syntax to the corresponding call.
For a binary plus operation, the left operand supplies the receiver and the right operand becomes an argument. Return a new value when addition should be immutable. A named method may be clearer if the operation does not resemble addition, even when the compiler would accept a plus function.
Do not overload symbols for unrelated actions such as sending an email or deleting a record. A reader expects + to combine or add, not produce a hidden external effect. Concise syntax is useful only when it makes the operation's meaning easier to recognize without inspecting the implementation.
Indexed access and membership
Get supports bracket reads with one or more indices. Set supports bracket writes and receives the assigned value as its last parameter. These conventions can be useful for a genuine matrix or collection. They still need bounds checks and a clear failure policy.
Contains supports membership syntax, with value in container translated through the container's contains operation. Define what membership means: exact identifier, equal value or a range predicate. Do not silently use substring matching when callers expect exact item membership.
Operator syntax does not remove validation. A get implementation can return null for an optional lookup or throw for an invalid position, depending on its declared contract. Prefer a named method when callers need a richer not-found result or when a bracket operation would hide expensive external I/O.
Comparison should form a consistent order
CompareTo returns a negative number, zero or a positive number and supports ordering operators such as < and >=. Use a consistent comparison key or lexicographic rule. Subtracting arbitrary integers can overflow, so a proper compareTo on the fields is safer than implementing ordering through raw subtraction.
Equality is a related but separate contract. A comparison result of zero should usually align with the domain's idea of equivalent ordering, while equals and hashCode must remain compatible with each other. Collections and sorting become confusing when an ordering groups values together but equality claims unrelated identities without documentation.
Keep tie-breakers explicit. Two products with equal prices can need name or identifier ordering for a reproducible screen. An operator on the whole product type may not represent every screen's sorting requirement, so a separate comparator can be better for context-specific order.
Mutation, compound assignment and increments
Plus and plusAssign express different possibilities. Plus normally produces a result, while plusAssign can mutate the receiver according to the permitted convention. Defining overlapping forms can create ambiguity in some assignment contexts. Choose a coherent immutable or mutable model rather than providing every possible operator automatically.
Inc and dec must return the value used for reassignment according to the operator rules; they should not mutate the receiver as a hidden extra effect. A custom increment still does not enforce a database uniqueness or concurrency guarantee. The language operation changes a value, not the surrounding persistence system.
The official operator reference lists required names and signatures. Follow those conventions precisely and document the domain meaning. An extension operator is resolved using Kotlin's extension rules, so it does not redefine another type's runtime dispatch globally.
Kotlin Operator Overloading Examples
Each example shows the code, the output it produces and what each part does.
Add two immutable scores
Plus creates another Score. The original remains four, which is a natural value-type expectation. The example uses small values; a real score or amount type may need overflow bounds. If currency is involved, also ensure both operands use a compatible currency rather than merely adding two integers.
data class Score(val points: Int) {
init { require(points >= 0) }
operator fun plus(other: Score): Score = Score(points + other.points)
}
fun main() {
val first = Score(4)
val combined = first + Score(3)
println(first.points)
println(combined.points)
}4
7Kotlin Operator Overloading in Real Projects
Worked exercise: a labeled score table
Create a ScoreTable containing a trusted map from labels to integer points. Support exact membership and an optional bracket lookup. Print the membership result and one known score, then show the missing result explicitly. This contract uses symbols in ways readers already understand from maps and collections.
class ScoreTable(private val scores: Map<String, Int>) {
operator fun contains(label: String): Boolean = scores.containsKey(label)
operator fun get(label: String): Int? = scores[label]
}
fun main() {
val table = ScoreTable(mapOf("blue" to 7, "green" to 4))
println("blue" in table)
println(table["blue"])
println(table["red"])
}true
7
nullThe map itself is private, and the exposed operations are read-only. The example assumes the supplied map remains under an appropriate ownership policy; a read-only Map interface is not a universal deep-immutability guarantee. Try a label with different case and decide whether exact matching is the intended rule before adding normalization.
Kotlin Operator Overloading FAQ
Can I create a completely new operator symbol?
No. Kotlin supports a predefined set of symbolic conventions with fixed precedence. You implement the corresponding recognized function names and signatures rather than inventing arbitrary syntax. A named function remains available for operations that do not fit those conventions. This limit is helpful because readers can rely on familiar parsing rules. Use symbolic syntax when its established meaning suits the domain, not as a way to make every business action look shorter.
Should plus mutate the original value?
For a value-like type, returning a new combined value is usually the clearest expectation. Mutation can be expressed through an appropriate plusAssign convention or a named method when needed. Keep the model consistent and make aliasing effects visible. If two variables share one mutable object, an operator that changes it can affect both callers. The syntax alone does not explain that ownership, so document and test it deliberately rather than relying on a reader to inspect the implementation.
Why is subtracting fields risky in compareTo?
The subtraction can overflow for large integer differences, producing a sign that contradicts the actual order. Use the fields' compareTo operation or a standard comparator instead. A comparison contract needs only negative, zero or positive, not the arithmetic distance between values. For compound ordering, compare the primary field and then a documented tie-breaker. This keeps the order consistent and avoids treating a convenient arithmetic expression as a reliable ordering algorithm.
When is a named method better than an operator?
Use a named method when the action has surprising side effects, several distinct failure states, substantial external work or a meaning that does not match the symbol. ApproveOrder is clearer than adding an approval object with + if approval changes persistent state. A well-chosen operator can simplify numeric and collection-like models, but ordinary domain verbs often communicate intent better. The goal is understandable behavior, not the maximum number of overloaded symbols in a class.
Kotlin Operator Overloading Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
A business operation sends a payment and can fail in several ways. What API is usually clearer?
Kotlin Operator Overloading Quiz
2 questions to check what you learned.
Which function convention supports a + b?
- operator fun plus(...)
- fun appendSymbol(...)
- private set alone
Which object supplies contains for value in container?
- Always the value on the left
- The container on the right
- A global namespace automatically