Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

The in keyword in Kotlin functions primarily as a statically resolved operator used for membership evaluation and iteration, and secondarily as a variance modifier (both declaration-site and use-site) in generics. At compile time, the Kotlin compiler desugars the in operator into specific underlying function calls based on its syntactic context and the static types of the operands.

1. Membership Evaluation (Containment)

When used as a binary operator in a boolean expression, in acts as syntactic sugar for the contains function. Operator resolution is strictly statically resolved at compile time based on the static types of the operands. If the contains function is provided via an extension function, the method invocation is statically dispatched. Desugaring mechanism:
a in b    // Translates to: b.contains(a)
a !in b   // Translates to: !b.contains(a)
Implementation requirements: For an object to support the in operator for membership, its class (or an extension function) must define a function named contains that:
  1. Is prefixed with the operator modifier.
  2. Accepts a parameter of the left-hand operand’s type.
  3. Returns a Boolean.
class CustomContainer {
    operator fun contains(element: String): Boolean {
        // Implementation logic
        return true
    }
}

2. Branch Conditions in when Expressions

The in and !in keywords function as distinct syntactic constructs when defining branch conditions within a when expression. The compiler evaluates whether the subject of the when expression is a member of the collection or range specified after the in keyword. This relies on the same statically resolved contains contract used in standard membership evaluation. Syntax:
when (subject) {
    in collection -> println("In collection") // Executes if collection.contains(subject) is true
    !in range -> println("Not in range")      // Executes if !range.contains(subject) is true
}

3. Iteration (Control Flow)

When used within a for loop declaration, the in operator dictates sequential iteration. It does not invoke contains; instead, it relies on the iterator contract. Like membership evaluation, the resolution of the required functions is statically resolved based on the operand’s static type. Desugaring mechanism:
for (item in collection) { /* ... */ }
Translates under the hood to a while loop utilizing an iterator:
val iterator = collection.iterator()
while (iterator.hasNext()) {
    val item = iterator.next()
    // ...
}
Implementation requirements: To support in for iteration, the right-hand operand must provide an iterator() function (member or extension) marked with the operator modifier. The object returned by iterator() must subsequently provide:
  1. operator fun next(): T
  2. operator fun hasNext(): Boolean

4. Generics (Contravariance Modifier)

While not an operator in this context, the in keyword is utilized in generics to denote contravariance. It restricts a type parameter to be consumed (passed as an argument into functions) and prevents it from being produced (returned from functions). This is applied at two levels: Declaration-site variance: Applied directly to the type parameter in the class or interface declaration. This instructs the compiler to allow safe subtyping globally for that type, meaning a Consumer<Number> can be safely assigned to a Consumer<Int>, reversing the standard subtyping relationship.
interface Consumer<in T> {
    fun consume(item: T)
    // fun produce(): T // Compilation error: T is declared as 'in'
}
Use-site variance (Type Projections): Applied to a type argument at the point of use. If a generic type is invariant at its declaration, applying in at the use-site projects it as contravariant. This restricts the operations available on the projected instance to only those that consume the type parameter, enabling safe subtyping for that specific usage.
// Array is invariant at the declaration site: class Array<T>
fun addAll(items: Array<in String>) {
    items[0] = "String" // Allowed: Consuming String
    // val item: String = items[0] // Compilation error: Projected as 'in', returns Any?
}
Master Kotlin with Deep Grasping Methodology!Learn More