skip to content

Operator & infix DSL Composition

Infix functions and operator conventions are what let a DSL read as prose: key to value, a shouldBe b, unaryPlus for content, get and set for lookup syntax. Used sparingly they clarify; overused they obscure, and interviewers want you to say so.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What is an infix function in Kotlin, and what are the rules for declaring one? Use it to explain how `1 to "a"` works.

level: juniorimportance: must knowfreq 70%

answer

  1. infix keyword + single param, no default, no vararg
  2. member or extension only
  3. a foo b == a.foo(b)
  4. to returns Pair, powers mapOf
  5. left-associative, looser than arithmetic

basics

~20 s

An infix function lets you call it without a dot or parentheses, like 1 to "a". You mark it with the infix keyword. It must be a member or extension function with exactly one parameter and no default value.

solid answer

~40 s

An `infix` function can be called between two operands without `.` or parentheses: `a foo b` means `a.foo(b)`. To qualify it must (1) be marked `infix`, (2) be a member function or an extension function, (3) take exactly one parameter, and (4) that parameter must not be `vararg` or have a default value. `1 to "a"` calls the standard-library `public infix fun <A, B> A.to(that: B): Pair<A, B>`, returning `Pair(1, "a")` — that is how `mapOf(1 to "a")` builds entries. Infix calls have lower precedence than arithmetic but higher than comparison/equality, and you cannot chain them without parentheses ambiguity rules. Infix is the backbone of readable DSLs because it removes punctuation noise.

code

kotlin · 7 lines
kotlin
infix fun Int.to(that: String): Pair<Int, String> = Pair(this, that)

val m = mapOf(1 to "a", 2 to "b")
println(m)  // {1=a, 2=b}

// Desugared form:
val p = 1.to("a")  // Pair(1, "a")

go deeper

for a junior

Knows the infix keyword, that it drops the dot/parentheses, and that to builds a Pair.

for a middle

States all four declaration rules and that it desugars to a normal method call.

for a senior

Discusses precedence/associativity and when infix helps vs. hurts readability and IDE discoverability.

for a principal

Frames infix as a DSL-design tool, weighing call-site clarity, API discoverability, and maintenance cost across a team.

## What `infix` is An **infix function** is called *between* its receiver and its single argument, with no dot and no parentheses. Syntactically `a foo b` is exactly `a.foo(b)`. ## Declaration rules A function qualifies as infix only if **all** of these hold: - It is marked with the `infix` keyword. - It is a **member function** (declared inside a class) or an **extension function** (declared on a receiver type). - It has **exactly one** parameter. - That parameter is **not** `vararg` and has **no default value**. ```kotlin infix fun Int.shouldBe(expected: Int) { require(this == expected) { "expected $expected but was $this" } } 3 shouldBe 3 // reads like English; same as (3).shouldBe(3) ``` ## How `1 to "a"` works The Kotlin standard library defines: ```kotlin public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that) ``` So `1 to "a"` is `1.to("a")`, which produces `Pair(1, "a")`. This is why `mapOf(1 to "a", 2 to "b")` reads naturally — `mapOf` takes `vararg pairs: Pair<K, V>`. ## Precedence and chaining - Infix calls bind **looser** than arithmetic operators (`+`, `*`) but **tighter** than named comparisons and the boolean operators. - `a infix1 b infix2 c` is **left-associative**: `(a infix1 b) infix2 c`. - You must use explicit parentheses when mixing infix with other operators if intent is unclear; e.g. `1 to 2 + 3` parses as `1 to (2 + 3)`. ## When to use it Use `infix` for **binary, symmetric-reading** operations (`to`, `and`, `until`, `step`, `shouldBe`). Avoid it for ordinary calls where a dot is clearer — overusing infix hurts readability and IDE discoverability.

  • Can a top-level function (no receiver) be infix?
    No. Infix requires a receiver, so it must be a member or an extension function — there is no left operand otherwise.
  • Why can't an infix function take a vararg or a default-valued parameter?
    Infix syntax provides exactly one explicit argument with no parentheses, so varargs and defaults would be unrepresentable/ambiguous; the compiler forbids them.

Infix is like saying 'two plus three' out loud instead of 'plus(two, three)' — the operator sits between the words.

saying these in an interview costs you the question

  • Claiming infix functions can take two or more parameters
  • Saying top-level non-extension functions can be infix
  • Thinking `to` is a language keyword rather than a stdlib extension function
  • Believing infix changes evaluation order beyond standard left-associativity
  • Confusing infix (any single-arg method) with operator overloading (fixed symbol set)

context

open as a page

How does operator overloading work in Kotlin? Name the function-name conventions for `+`, `[]` get/set, `in`, and `..`, and explain what `operator` does.

level: middleimportance: must knowfreq 60%

basics

~20 s

Kotlin maps symbols to specially named functions. + calls plus, a[i] calls get, a[i]=v calls set, x in c calls contains, a..b calls rangeTo. You mark each with the operator keyword so the compiler allows the symbolic call.

open as a page

You're designing a tiny assertion DSL with `infix fun <T> T.shouldBe(expected: T)`. Walk through how the infix call resolves, generics, nullability, and one limitation you'd warn the team about.

level: seniorimportance: should knowfreq 35%

basics

~20 s

actual shouldBe expected calls actual.shouldBe(expected). The generic T is inferred from both sides, so types must line up. You should warn that infix doesn't chain cleanly and equality uses equals, which can surprise with platform types or floating-point.

open as a page

Explain the `unaryPlus` and `invoke` conventions and how they make builder-style DSLs read naturally (e.g. `+"text"` inside an HTML builder, or `dependencies { implementation(...) }`).

level: seniorimportance: should knowfreq 45%

basics

~20 s

unaryPlus lets +x mean 'add x', so +"hello" inside a builder appends text. invoke lets you call an object like a function — thing(args) runs thing.invoke(args). Both remove method-name noise so DSL blocks read cleanly.

open as a page

How do the `getValue`/`setValue`/`provideDelegate` operator conventions support `by`-delegated properties, and how is this different from the `get`/`set` indexing operators?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Property delegation (val x by delegate) uses getValue and setValue operator functions on the delegate, with an optional provideDelegate to customize creation. The get/set operators are different — they back the [] indexing syntax, not the by keyword.

open as a page