skip to content

Range Membership & Char Ranges

x in range is a readable membership test that compiles to contains, and it works for character ranges like 'a'..'z' too. It is the idiomatic replacement for chained comparison conditions in validation code.

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

questions

5

In Kotlin, how do you check whether a value lies inside a range, and what does the `in` operator do under the hood for a range like `1..10`?

level: juniorimportance: must knowfreq 78%

answer

  1. `a in b` == `b.contains(a)`
  2. `..` ranges are inclusive at BOTH ends
  3. membership is O(1) comparison, not iteration
  4. `!in` negates
  5. great inside `when` branches

basics

~10 s

Use the in keyword: x in 1..10 is true when x is between 1 and 10, both ends included. !in checks the opposite. It calls the range's contains method.

solid answer

~40 s

Write `value in range` to test membership; it returns a `Boolean`. For `1..10` (an `IntRange`, both endpoints inclusive), `x in 1..10` compiles to `(1..10).contains(x)`. The `in` operator is sugar for the `contains` operator function, and `!in` negates it. Because `IntRange` overrides `contains` with a simple `first <= value && value <= last` comparison (no iteration), the check is O(1). You can use `in` in `if`, `while`, and especially `when` branches: `when (x) { in 1..10 -> ... }`. The same syntax works for `CharRange` and `LongRange`. Mixing types matters: `1.5 in 1..10` won't compile because `1..10` is an `IntRange`; you'd use `1.0..10.0` (a `ClosedFloatingPointRange`) instead.

code

kotlin · 9 lines
kotlin
val temp = 23
val comfortable = temp in 18..26      // true, == (18..26).contains(23)
val label = when (temp) {
    in Int.MIN_VALUE..0 -> "freezing"
    in 1..17            -> "cold"
    in 18..26           -> "comfortable"
    else                -> "hot"
}
println(label)                          // comfortable

go deeper

for a junior

Knows x in 1..10, that both ends are inclusive, and that !in negates.

for a middle

Explains in desugars to contains and that numeric membership is an O(1) comparison, not a loop.

for a senior

Connects in to the operator-overloading convention and notes the type-matching rule (Int vs Double ranges).

for a principal

Discusses when in semantics differ across range types and why contains overrides matter for performance and API design.

## What `in` means for ranges A **range** in Kotlin is an object describing a closed interval. `1..10` creates an `IntRange` whose endpoints are **both inclusive** (1 and 10 are members). The `in` operator answers the yes/no question "is this value a member?" and returns a `Boolean`. ## `in` is operator sugar for `contains` Kotlin translates `a in b` into `b.contains(a)` — the receiver is the range on the **right**, the candidate value is on the **left**. `!in` translates to `!b.contains(a)`. This is the **operator-overloading convention**: any type that declares an `operator fun contains(...)` supports `in`. ```kotlin val r = 1..10 println(5 in r) // true -> r.contains(5) println(0 in r) // false println(10 in r) // true (last endpoint is inclusive) println(7 !in r) // false -> !r.contains(7) ``` ## It's a comparison, not a loop For numeric ranges, `contains` does **not** iterate. `IntRange.contains` is essentially `first <= value && value <= last`, so the test is **O(1)** regardless of how big the range is — `1_000_000 in 1..1_000_000_000` is instant. ## Works anywhere a Boolean is expected ```kotlin if (score in 0..100) { /* valid */ } while (i in 1..n) { /* ... */ } val grade = when (score) { in 90..100 -> "A" in 80..89 -> "B" else -> "F" } ``` The `when (subject)` form is a very common place to use `in` because each branch reads as a clean membership test. ## Type must match the range `1..10` is an `IntRange`. Putting a `Double` on the left (`1.5 in 1..10`) fails to compile because there is no `contains(Double)` on `IntRange`. For floating-point intervals use `1.0..10.0`, which is a `ClosedFloatingPointRange<Double>`.

  • What does `x !in 1..10` compile to?
    `!(1..10).contains(x)` — the same `contains` call, negated.
  • Is `10 in 1..10` true or false?
    True. The `..` operator produces a range that is inclusive on both ends, so the last endpoint is a member.

in is like asking a bouncer 'is my number on the guest list between 1 and 10?' — one glance, not reading every name.

saying these in an interview costs you the question

  • Saying `in` iterates the whole range to find the value (it's a comparison for numeric ranges).
  • Claiming the upper bound is exclusive for `..` (that's `until`, a sibling topic).
  • Writing `1..10 in x` with operands reversed.
  • Thinking `1.5 in 1..10` compiles.

context

open as a page

How do char ranges like `'a'..'z'` work for character classification, and how would you test if a Char is an ASCII letter or digit using ranges?

level: middleimportance: should knowfreq 64%

basics

~10 s

'a'..'z' is a range of characters in code-point order. c in 'a'..'z' is true for lowercase letters. Combine ranges with || to test letters or digits.

open as a page

The `in` operator works on both ranges and collections (e.g. `x in list`). How do these differ in semantics and performance, and what surprises can `in` produce with strings and reversed ranges?

level: middleimportance: should knowfreq 52%

basics

~20 s

in always calls contains. For a range it's a quick comparison; for a list it scans elements (equals); for a Set it's a fast hash lookup; for a String it checks substrings. Reversed numeric ranges are empty.

open as a page

Explain the relationship between the `in` operator and `ClosedRange.contains`. When does `in` dispatch to `ClosedRange.contains` versus a more specialized override, and why does it matter?

level: seniorimportance: should knowfreq 48%

basics

~10 s

x in range calls range.contains(x). ClosedRange provides a default contains using compareTo. Specialized ranges like IntRange override it with faster primitive comparisons.

open as a page

How would you enable the `in` operator for membership tests against an interval of your own domain type, and what are the correctness pitfalls (Comparable consistency, half-open intervals, thread safety)?

level: principalimportance: nice to knowfreq 22%

basics

~10 s

Make your type Comparable, then build a ClosedRange (or define your own operator fun contains). After that, value in interval just works. Ensure your ordering is consistent and total.

open as a page