skip to content

where-Clause Multiple Constraints

When one bound is not enough, the where clause lets you require several at once, such as T : CharSequence and T : Comparable<T>. It is Kotlin's answer to Java's ampersand-joined bounds.

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

questions

5

In Kotlin generics, what does the `where` clause do, and how does it differ from writing an upper bound inline like `<T : CharSequence>`?

level: juniorimportance: must knowfreq 55%

answer

  1. Inline brackets allow only ONE bound
  2. `where` = multiple upper bounds, all required (AND)
  3. `where` sits after return type, before body
  4. Comma-separated `T : X, T : Y`
  5. Compile-time only, erased at runtime

basics

~20 s

The where clause lets a type parameter require more than one constraint at once. Inline syntax like <T : CharSequence> can only state one bound, so when you need two or more you move them into where.

solid answer

~40 s

An upper bound restricts which types can be substituted for a type parameter. Inline angle-bracket syntax (`fun <T : CharSequence> f(t: T)`) supports exactly one bound per parameter. When a parameter must satisfy several bounds at once, you list the parameter without a bound in the brackets and add a `where` clause after the signature: `fun <T> f(t: T) where T : CharSequence, T : Comparable<T>`. Every comma-separated constraint must hold simultaneously (logical AND), so only types that are both a `CharSequence` and `Comparable<T>` are accepted. The `where` clause goes before the function body / class body. It is purely a compile-time constraint with no runtime cost; bounds are erased like all generic type information.

code

kotlin · 8 lines
kotlin
// Single bound (inline)
fun <T : CharSequence> len(t: T) = t.length

// Multiple bounds (where) — T must be BOTH
fun <T> pick(a: T, b: T): T where T : CharSequence, T : Comparable<T> =
    if (a >= b) a else b

val r = pick("apple", "pear") // String is CharSequence + Comparable

go deeper

for a junior

Knows where allows more than one bound and that inline allows only one.

for a middle

States the AND semantics and correct placement after the return type, before the body.

for a senior

Connects it to type erasure and explains there is no runtime cost or runtime inspectability.

for a principal

Frames where as the only way to express a conjunction of bounds and discusses API design trade-offs of demanding several capabilities from one parameter.

## What an upper bound is A generic type parameter (the `T` in `<T>`) is a placeholder for a real type chosen by the caller. An **upper bound** says "the type filling in for `T` must be a subtype of X." The default upper bound is `Any?` (anything, nullable included). ## Single bound: inline syntax For one bound you write it in the angle brackets: ```kotlin fun <T : CharSequence> firstChar(t: T): Char = t[0] ``` Here `T` must be a `CharSequence`, so `String` and `StringBuilder` work but `Int` does not. ## Why `where` exists The inline form allows **only one** bound per parameter. The `where` clause removes that limit: it lets you impose **multiple** upper bounds on the same parameter, all of which must hold at the same time (a logical AND). ```kotlin fun <T> maxLabel(items: List<T>): T where T : CharSequence, T : Comparable<T> = items.max() ``` `T` must be **both** a `CharSequence` **and** `Comparable<T>`. `String` satisfies both, so `String` is a valid `T`. ## Syntax placement - The parameter is declared **without** a bound inside the brackets: `<T>`. - The `where` clause comes **after** the return type (for functions) or the supertype list (for classes), and **before** the body. - Constraints are comma-separated: `where T : A, T : B`. ## Key facts - It is a **compile-time** check only. Generics are erased at runtime, so the bounds add no runtime cost and cannot be inspected via reflection on the type variable. - Each constraint is an independent `T : Something`; you cannot write `T : A & B` inline. - You can constrain multiple parameters in one `where`: `where T : A, U : B`.

  • Can you put two bounds inline, like `<T : A, T : B>`?
    No. Inline syntax permits only one bound per parameter; repeating `T` in the brackets is a compile error. Multiple bounds require the `where` clause.
  • Do the `where` constraints combine as AND or OR?
    AND. The substituted type must satisfy every listed constraint simultaneously.

Inline <T : X> is a single entry requirement; the where clause is a checklist where every box must be ticked.

saying these in an interview costs you the question

  • Claiming you can write multiple inline bounds with commas inside the brackets
  • Saying the constraints are an OR (any one suffices)
  • Thinking `where` adds runtime checks or runtime cost
  • Confusing `where` (upper bounds) with variance (`in`/`out`)

context

open as a page

Write a generic function whose type parameter must be both a `CharSequence` and `Comparable<T>`. Show exactly where the `where` clause goes in a function and in a class declaration.

level: middleimportance: must knowfreq 45%

basics

~10 s

Declare the parameter as plain <T>, then after the return type (function) or the supertypes (class) add where T : CharSequence, T : Comparable<T> before the body.

open as a page

Inside a function with `where T : CharSequence, T : Comparable<T>`, which members of `T` can you call, and why does combining bounds widen the available API?

level: middleimportance: should knowfreq 38%

basics

~20 s

You can use every member from all the bounds at once. With CharSequence plus Comparable<T> you get length, indexing, and compareTo/the comparison operators on the same value, because T is effectively the intersection of the bounds.

open as a page

A teammate writes `fun <T> mid(a: T, b: T) where T : Number, T : Comparable<T>` and calls `mid(1, 2)`. It compiles, but `mid(1.0, 2)` does not. Explain why, and what the multi-bound `where` actually requires of the argument types.

level: seniorimportance: should knowfreq 30%

basics

~20 s

T must be one single type that is both a Number and Comparable to itself. mid(1, 2) infers T = Int, which qualifies. mid(1.0, 2) mixes Double and Int, so no single T satisfies both arguments and the bounds, and inference fails.

open as a page

When designing a public API, how would you decide between a multi-bound `where` clause (e.g. `where T : Persistable, T : Auditable`) versus introducing a single combined interface? What are the trade-offs?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Use a multi-bound where when callers' types already implement the separate interfaces and you don't want to force them to add a new marker. Introduce a combined interface only when the combination is a real, reusable domain concept worth naming.

open as a page