When is it appropriate to add the `infix` modifier to an API, and what are the design pitfalls? Give an example of a well-designed infix function.
answer
- Infix = readability tool, not a default
- Use when `a name b` reads as a clear phrase
- Order must be obvious from the name
- Beware precedence + discoverability
- Kotest `shouldBe`, MockK `returns` are references
basics
~20 sUse infix only when the call reads like a natural two-word phrase (x shouldBe y). Avoid it when the operand order is unclear, when there are side effects, or when precedence with other operators could confuse readers.
solid answer
~40 sAdd `infix` when a single-argument function genuinely reads better as `a name b` — typically in DSLs, assertion libraries (`result shouldBe 42`, `list shouldContain x`), bit manipulation, and pair/range building. Good infix names are verbs or relational words where the left-to-right order is unambiguous. Pitfalls: (1) precedence — infix sits below arithmetic but above `==`/`&&`, so mixing it with other operators surprises readers; (2) discoverability — IDE auto-complete after a dot is weaker for infix-style usage; (3) ambiguous operand order (`a divideInto b` — which is numerator?); (4) overuse turns code into a puzzle. Keep infix functions pure or clearly side-effect-named, single-purpose, and well-named so the phrase is self-documenting. Kotest, MockK, and the stdlib are the reference style.
code
kotlin · 8 lines// Good: clear phrase, obvious order, pure relational check
infix fun Int.hasBit(flag: Int): Boolean = (this and flag) != 0
val perms = 0b101
if (perms hasBit 0b001) println("read")
// Questionable: ambiguous operand order — avoid infix here
infix fun Double.dividedBy(x: Double) = this / x // which is numerator? at least the name helpsgo deeper
Recognizes infix is for readability and names a good example like to or shouldBe.
Lists the single-param/no-default rules and gives a sensible DSL use case.
Weighs precedence, discoverability, operand-order ambiguity, and side-effect concerns when deciding to use infix.
Sets team/library conventions for infix usage, balancing API ergonomics, precedence safety, and maintainability across a codebase.
## When infix earns its keep The `infix` modifier is a **readability tool**, not a default. Apply it when ALL of these hold: - The function takes exactly one logical argument and the call site reads like a **natural phrase**: `1 to 2`, `result shouldBe 200`, `flags hasFlag READ`. - The **operand order is obvious** from the name. Relational/verb names (`shouldBe`, `until`, `contains`, `shl`) make `a name b` unambiguous. - It fits a **DSL or fluent** context where prose-like code adds clarity. ```kotlin // Assertion-style DSL (Kotest flavor) infix fun <T> T.shouldBe(expected: T) { if (this != expected) throw AssertionError("expected $expected but was $this") } @Test fun t() { (2 + 2) shouldBe 4 // reads like a sentence } ``` ## Pitfalls to weigh - **Precedence surprises.** Infix binds below arithmetic/range but above comparison, equality, `&&`/`||`, and `?:`. Mixing infix with these without parentheses misleads readers (e.g. `a == b and c` = `a == (b and c)`). - **Discoverability.** After typing `a.`, the IDE lists methods; the infix `a name` form is less guided, so obscure infix APIs are harder to find. - **Ambiguous order.** Names like `a divideInto b` or `a from b` don't make clear which operand is which — avoid infix there. - **Side effects.** Infix reads declaratively, so a function that mutates or performs IO can mislead; name it clearly or skip infix. - **Overuse.** A chain like `a foo b bar c baz d` is a puzzle. Reserve infix for one or two clear phrases. ## Design checklist - Single param, no vararg/default (hard requirements). - Verb or relational name, left-to-right reading unambiguous. - Prefer pure / no surprising effects. - Provide the dot form too (it's automatic) for discoverability. - Document precedence-sensitive usage; encourage parentheses when combined with operators. ## Reference libraries - **stdlib**: `to`, `until`, `downTo`, `step`, `shl`/`and`/`or`. - **Kotest**: `shouldBe`, `shouldContain`, `shouldHaveSize`. - **MockK**: `every { ... } returns x`, `andThen`. These succeed because each infix call is a short, unambiguous, prose-like phrase.
- Why might overusing infix hurt discoverability?IDE auto-complete is strongest after a `.`; infix-style usage isn't prompted the same way, so obscure infix APIs are harder to discover than dot-called methods.
- Should an infix function have side effects?Prefer not. Infix reads declaratively like an expression, so hidden mutation or IO is surprising. If effects are essential, give it an imperative name and consider skipping infix.
Infix is seasoning: a pinch on the right dish (a DSL) elevates it; dumped on everything it ruins the meal.
saying these in an interview costs you the question
- Marking every single-param function infix by default
- Choosing infix names with ambiguous operand order
- Ignoring precedence when the API mixes with operators
- Hiding side effects behind declarative-looking infix calls
- Long infix chains that obscure evaluation order