# How to Define Custom Conditions for a Kotlin When Expression: 3 Advanced Techniques

> Master Kotlin when expressions with custom conditions. Learn 3 advanced techniques including omitted-subject syntax, contains operator, and sealed class hierarchies for powerful logic.

- Repository: [JetBrains/kotlin](https://github.com/jetbrains/kotlin)
- Tags: deep-dive
- Published: 2026-02-15

---

**You can define custom conditions in Kotlin `when` expressions by using omitted-subject syntax for arbitrary Boolean logic, implementing the `contains` operator for custom `in` checks, or leveraging sealed class hierarchies for exhaustive type matching.**

Kotlin’s `when` construct offers far more flexibility than traditional switch statements, allowing you to tailor branch conditions to complex domain logic. Whether you need to check membership in custom ranges, validate against business rules, or handle sealed type hierarchies, the compiler supports these patterns through specific PSI and FIR representations.

## Three Ways to Create Custom Conditions in Kotlin When Expressions

### Omitted-Subject When Expressions for Arbitrary Boolean Logic

The most straightforward way to implement custom conditions is to omit the `when` subject entirely. In this form, each branch evaluates an independent Boolean expression, allowing you to combine multiple variables and complex predicates.

```kotlin
val x = 7
val y = 12

val result = when {
    x < 0 && y < 0 -> "both negative"
    x > 0 && y > 0 -> "both positive"
    x % 2 == 0    -> "x is even"
    else          -> "fallback"
}

```

This pattern is parsed into the Kotlin compiler’s PSI structure in `KtWhenExpression` located at [`compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtWhenExpression.java`](https://github.com/JetBrains/kotlin/blob/main/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtWhenExpression.java), where the absence of a subject expression triggers Boolean evaluation mode for each entry.

### Custom Infix Conditions Using the In Operator

You can create domain-specific membership tests by implementing the `contains` operator in your own classes. When you use `in` or `!in` in a `when` expression with a subject, Kotlin invokes `operator fun contains(element: T): Boolean` on the right-hand side.

```kotlin
data class TemperatureRange(val low: Int, val high: Int) {
    operator fun contains(value: Int): Boolean = value in low..high
}

val cold = TemperatureRange(-30, 0)
val warm = TemperatureRange(1, 25)

val temp = 18

val description = when (temp) {
    in cold -> "Freezing"
    in warm -> "Comfortable"
    else    -> "Hot"
}

```

This technique allows you to encapsulate any validation logic—regex matching, database lookups, or geometric containment—into reusable condition objects. The FIR (Front-end Intermediate Representation) handling for these expressions is implemented in `FirWhenExpressionImpl` at [`compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/impl/FirWhenExpressionImpl.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/impl/FirWhenExpressionImpl.kt).

### Type-Based Conditions with Sealed Class Hierarchies

For exhaustive custom conditions based on types, define a **sealed class** hierarchy. The compiler guarantees that your `when` expression handles every possible subtype, effectively turning type checks into compile-time verified conditions.

```kotlin
sealed class UiState
data class Loading(val percent: Int) : UiState()
object Success : UiState()
object Error   : UiState()

fun render(state: UiState) = when (state) {
    is Loading -> "Loading ${state.percent}%"
    Success   -> "All good!"
    Error     -> "Something went wrong"
}

```

Because `UiState` is sealed, the compiler knows all possible implementations at compile time. If you add a new subclass, the compiler will flag incomplete `when` expressions. This safety mechanism is constructed during FIR building in `FirWhenExpressionBuilder` at [`compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/builder/FirWhenExpressionBuilder.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/builder/FirWhenExpressionBuilder.kt).

## Implementation Details in the Kotlin Compiler

The Kotlin compiler processes `when` expressions through three main components in the JetBrains/kotlin repository:

- **`KtWhenExpression`** ([`compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtWhenExpression.java`](https://github.com/JetBrains/kotlin/blob/main/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtWhenExpression.java)) – Represents the PSI (Program Structure Interface) node for parsing `when` syntax, handling both subject-present and subject-omitted forms.

- **`FirWhenExpressionImpl`** ([`compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/impl/FirWhenExpressionImpl.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/impl/FirWhenExpressionImpl.kt)) – The FIR implementation that models `when` expressions during semantic analysis, including support for custom `in` and `is` conditions.

- **`FirWhenExpressionBuilder`** ([`compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/builder/FirWhenExpressionBuilder.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/builder/FirWhenExpressionBuilder.kt)) – Constructs FIR nodes for `when` expressions, ensuring exhaustive checking for sealed classes and proper resolution of custom condition operators.

## Practical Code Examples

Here are complete, runnable examples demonstrating each custom condition technique:

```kotlin
// Example 1: Omitted subject with complex logic
fun classify(x: Int, y: Int) = when {
    x == y -> "equal"
    x > y  -> "x is larger"
    else   -> "y is larger"
}

// Example 2: Custom membership test
class PrimeRange {
    operator fun contains(value: Int): Boolean = 
        value > 1 && (2..value/2).none { value % it == 0 }
}

val prime = PrimeRange()
val n = 13
val msg = when (n) {
    in prime -> "$n is prime"
    else     -> "$n is composite"
}

// Example 3: Exhaustive sealed class handling
sealed class Result
data class Ok(val data: String) : Result()
object NotFound : Result()
object Unauthorized : Result()

fun handle(r: Result) = when (r) {
    is Ok        -> "Got ${r.data}"
    NotFound     -> "Missing"
    Unauthorized -> "Denied"
}

```

## Summary

- **Omitted-subject syntax** allows arbitrary Boolean expressions as custom conditions, parsed via `KtWhenExpression` in the compiler’s PSI layer.
- **Custom `in` conditions** leverage the `contains` operator to encapsulate domain-specific validation logic, resolved during FIR generation in `FirWhenExpressionImpl`.
- **Sealed class hierarchies** enable exhaustive, type-safe custom conditions with compile-time verification, constructed through `FirWhenExpressionBuilder`.

## Frequently Asked Questions

### Can I use multiple conditions in a single when branch?

Yes, you can combine multiple conditions using `||` (or) logic by placing them on the same line with commas, or by using separate `when` entries. However, Kotlin does not support `&&` (and) within a single branch condition directly; for complex conjunctions, use the omitted-subject form where each branch is a full Boolean expression.

### How does the in operator work with custom conditions?

The `in` operator invokes the `contains` method on the right-hand operand. When you write `value in container`, the compiler translates this to `container.contains(value)`. By defining `operator fun contains(element: T): Boolean` in your own classes, you create custom membership tests that integrate seamlessly with `when` expressions, as implemented in the FIR layer via `FirWhenExpressionImpl`.

### Are sealed classes required for exhaustive when expressions?

Sealed classes are not strictly required, but they are the only mechanism that provides compile-time exhaustiveness checking for type-based conditions. If you use open classes or interfaces, the compiler will require an `else` branch because it cannot determine all possible subtypes. Sealed classes restrict the hierarchy to a known set of types, allowing the compiler—through `FirWhenExpressionBuilder`—to verify that all cases are handled without a default branch.

### What is the performance impact of custom when conditions?

Custom conditions using the omitted-subject form or custom `contains` operators have negligible performance overhead compared to standard `if-else` chains, as they compile down to similar bytecode. The `in` operator with custom containers is inlined when possible. Sealed class type checks use the `instanceof` operator at the JVM level, which is highly optimized. According to the Kotlin compiler implementation in `FirWhenExpressionImpl`, these constructs are resolved during compilation rather than runtime, ensuring efficient execution.