Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions concepts/when/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"authors": [
"kahgoh"
],
"contributors": [],
"blurb": "When is an alternative to long if / else if / else chains."
}
147 changes: 147 additions & 0 deletions concepts/when/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Introduction

Kotlin's `when` is a conditional expression for choosing among several options.
It is similar to `switch` in Java, and more flexible than a chain of `if` ... `else if` ... `else`.

Like `if`, `when` can also be used as an expression that produces a value.

## Matching a value

Give `when` a value to match in parentheses.
Each branch compares that value until one matches:

```kotlin
val animal = "chicken"
val type = when (animal) {
"chicken" -> "bird"
"poodle" -> "dog"
else -> "unknown"
}
// type => "bird"
```

- Each branch is written as `pattern -> result`.
- Branches are tried from top to bottom until a match is found.
Only the first branch runs.
- Use `else` for any value not listed above (similar to the final `else` in an `if` chain).

Comment on lines +23 to +27

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is pattern matching then?

Could be something to call out!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added mention about pattern matching.

This is similar to [pattern matching][pattern-matching].
The `pattern` can be a value to match, or use `is` to match by type:

```kotlin
fun typeText(subject: Any): String {
return when(subject) {
is Animal -> "Animal"
is Fruit -> "Fruit"
is String -> "Text"
else -> "Unknown"
}
}
```

or use `in` to check if it is contained in a range, collection, string or anything else with a `contains` method:

```kotlin
fun describe(number: Int, position: String) {
// Range
when(number) {
in 1..10 -> println("From 1 to 10")
in 11..20 -> println("From 11 to 20")
}

// Collection
when(number) {
in listOf(2, 4, 6) -> println("small even")
in listOf(3, 5, 7) -> println("small odd")
}

// String
when(position) {
in "center middle" -> println("inner")
in "left right boundary" -> println("outer")
}
}
```

~~~exercism/note
You can also use `in` with other objects by defining the `contains` [operator extension function][kotlin-operator-overloading].
For example, using it with [Regex][kotlin-regex]:

```kotlin
operator fun Regex.contains(text: CharSequence): Boolean = this.matches(text)

fun replace(input: String) {
return when(input) {
in Regex("""ani*""") -> println("animal")
in Regex("""fru.*""") -> println("fruit")
in Regex("""num.*""") -> println("number")
else -> println("unknown")
}
}
```
~~~

## Guard conditions

When matching a value, additional conditions can be placed in an `if` after the pattern.
These conditions are called guard conditions.

```kotlin
val x = 5
val y = 10
val description = when (x) {
5 if y % 2 == 0 -> "x is 5, y is even"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does regexp work?

@kahgoh kahgoh Aug 3, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, regexp can work as part of the guard. Something like this:

val code = 3
val classification = "animal"
val type = when (code) {
    3 if """.*mal""".toRegex().matches(classification) -> "animal"
    else -> "unknown"
}

But if using it match the value, I think you can only do it by matching conditions (i.e. without a value to match). For example:

val classification = "animal"
val type = when {
    Regex("''.*mal""").matches(classification) -> "animal"
    Regex(''"fru.*""").matches(classification) -> "fruit"
    else -> "unknown"
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I just learn I can indeed use Regexp but would need to define an extension function:

operator fun Regex.contains(text: CharSequence): Boolean = this.matches(text)

fun main() {
    val classification = "animal"
    val res = when(classification) {
        in Regex(""".*mal""") -> println("animal")
        in Regex("""fru.*""") -> println("fruit")
        else -> println("unknown")
    }

    println(res)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added something about this in the about.md.

else -> "other"
}
// description => "x is 5, y is even"
```

Even `else if` can be used.

```kotlin
val x = 7
val y = 10
val description = when (x) {
5 if y % 2 == 0 -> "x is 5, y is even"
else if y % 2 == 0 -> "y is an even number"
else -> "other"
}
// description => "y is an even number"
```


## Matching conditions

Alternatively, the value can be omitted and boolean conditions can be placed on each branch:

```kotlin
val x = 5
val y = 8
val description = when {
x % 2 != 0 -> "x is odd"
y % 2 == 0 -> "y is even"
else -> "neither condition matched first"
}
// description => "x is odd"
```

Again, branches are checked from top to bottom and only the first branch whose condition is `true` runs.

## Using `when` as an expression

When `when` is used for its result (assigned to a variable, returned from a function, and so on), every possible case must be covered.

- When matching with a value, add `else` unless every possible value is already handled (for example, all values of a `Boolean` or an `enum`).
- When matching conditions, an `else` branch is required, because the compiler cannot know that your conditions cover every case.

```kotlin
fun label(n: Int): String = when (n) {
0 -> "zero"
1 -> "one"
else -> "other"
}
```

[kotlin-operator-overloading]: https://kotlinlang.org/docs/operator-overloading.html
[kotlin-regex]: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/-regex/
[pattern-matching]: https://en.wikipedia.org/wiki/Pattern_matching
127 changes: 127 additions & 0 deletions concepts/when/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Introduction

Kotlin's `when` is a conditional expression for choosing among several options.
It is similar to `switch` in Java, and more flexible than a chain of `if` ... `else if` ... `else`.

Like `if`, `when` can also be used as an expression that produces a value.

## Matching a value

Give `when` a value to match in parentheses.
Each branch compares that value until one matches:

```kotlin
val animal = "chicken"
val type = when (animal) {
"chicken" -> "bird"
"poodle" -> "dog"
else -> "unknown"
}
// type => "bird"
```

- Each branch is written as `pattern -> result`.
- Branches are tried from top to bottom until a match is found.
Only the first branch runs.
- Use `else` for any value not listed above (similar to the final `else` in an `if` chain).

This is similar to [pattern matching][pattern-matching].
The `pattern` can be a value to match, or use `is` to match by type:

```kotlin
fun typeText(subject: Any): String {
return when(subject) {
is Animal -> "Animal"
is Fruit -> "Fruit"
is String -> "Text"
else -> "Unknown"
}
}
```

or use `in` to check if it is contained in a range, collection, string or anything else with a `contains` method:

```kotlin
fun describe(number: Int, position: String) {
// Range
when(number) {
in 1..10 -> println("From 1 to 10")
in 11..20 -> println("From 11 to 20")
}

// Collection
when(number) {
in listOf(2, 4, 6) -> println("small even")
in listOf(3, 5, 7) -> println("small odd")
}

// String
when(position) {
in "center middle" -> println("inner")
in "left right boundary" -> println("outer")
}
}
```

## Guard conditions

When matching a value, additional conditions can be placed in an `if` after the pattern.
These conditions are called guard conditions.

```kotlin
val x = 5
val y = 10
val description = when (x) {
5 if y % 2 == 0 -> "x is 5, y is even"
else -> "other"
}
// description => "x is 5, y is even"
```

Even `else if` can be used.

```kotlin
val x = 7
val y = 10
val description = when (x) {
5 if y % 2 == 0 -> "x is 5, y is even"
else if y % 2 == 0 -> "y is an even number"
else -> "other"
}
// description => "y is an even number"
```


## Matching conditions

Alternatively, the value can be omitted and boolean conditions can be placed on each branch:

```kotlin
val x = 5
val y = 8
val description = when {
x % 2 != 0 -> "x is odd"
y % 2 == 0 -> "y is even"
else -> "neither condition matched first"
}
// description => "x is odd"
```

Again, branches are checked from top to bottom and only the first branch whose condition is `true` runs.

## Using `when` as an expression

When `when` is used for its result (assigned to a variable, returned from a function, and so on), every possible case must be covered.

- When matching with a value, add `else` unless every possible value is already handled (for example, all values of a `Boolean` or an `enum`).
- When matching conditions, an `else` branch is required, because the compiler cannot know that your conditions cover every case.

```kotlin
fun label(n: Int): String = when (n) {
0 -> "zero"
1 -> "one"
else -> "other"
}
```

[pattern-matching]: https://en.wikipedia.org/wiki/Pattern_matching
6 changes: 6 additions & 0 deletions concepts/when/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"url": "https://kotlinlang.org/docs/control-flow.html#when-expressions-and-statements",
"description": "Kotlin when expressions and statements"
}
]
41 changes: 29 additions & 12 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
},
{
"slug": "log-levels",
"name": "log-levels",
"name": "Log Levels",
"uuid": "ef54c5e6-7d31-42d1-a300-e405169dbd7f",
"concepts": [
"strings"
Expand All @@ -68,6 +68,18 @@
],
"status": "wip"
},
{
"slug": "guessing-game",
"name": "Guessing Game",
"uuid": "8ef61603-c6c0-4188-8436-9bd194a2850f",
"concepts": [
"when"
],
"prerequisites": [
"conditionals"
],
"status": "wip"
},
{
"slug": "secret-agent",
"name": "secret-agent",
Expand Down Expand Up @@ -1430,26 +1442,26 @@
"slug": "basics",
"name": "Basics"
},
{
"uuid": "1050abed-4271-4519-a44c-a0ca121c7de2",
"slug": "bitwise-operations",
"name": "Bitwise Operations"
},
{
"uuid": "53e0b729-810e-4042-9477-8c8d05a7b302",
"slug": "booleans",
"name": "Booleans"
},
{
"uuid": "157bbe3b-e4f5-41f7-8e0e-e2f57d526617",
"slug": "strings",
"name": "Strings"
"uuid": "660e1acd-3072-4bbc-ae44-bbc204da5eab",
"slug": "chars",
"name": "Chars"
},
{
"uuid": "168827c0-4867-449a-ad22-611c87314c48",
"slug": "conditionals",
"name": "Conditionals"
},
{
"uuid": "660e1acd-3072-4bbc-ae44-bbc204da5eab",
"slug": "chars",
"name": "Chars"
},
{
"uuid": "c26a1939-deb9-4df7-85c3-f505cdf89e8c",
"slug": "higher-order-functions",
Expand All @@ -1461,9 +1473,14 @@
"name": "Nullability"
},
{
"uuid": "1050abed-4271-4519-a44c-a0ca121c7de2",
"slug": "bitwise-operations",
"name": "Bitwise Operations"
"uuid": "157bbe3b-e4f5-41f7-8e0e-e2f57d526617",
"slug": "strings",
"name": "Strings"
},
{
"uuid": "86b469a5-c21b-4c8b-98c8-942ca210fe36",
"slug": "when",
"name": "When"
}
],
"key_features": [
Expand Down
Loading
Loading