skip to content

How does the upper bound of a type parameter affect which members and operations you can call on a value of that type inside the function body?

level: middleimportance: should knowfreq 40%

answer

  1. Body sees T as its bound's API
  2. Any? bound = toString/equals/hashCode only
  3. Tighter bound unlocks more members
  4. Bound restricts callers AND empowers body
  5. Pick loosest bound that gives needed ops

basics

~10 s

Inside the function, a value of type T only exposes the members of its upper bound. With no bound (Any?) you get almost nothing; with <T : Number> you get Number's methods like toInt().

solid answer

~40 s

An upper bound defines the **static API surface** of a type parameter. Inside the body, the compiler only knows that a `T` value is *some* subtype of the bound, so it permits exactly the members declared on the bound (plus extensions in scope). With the implicit `Any?` bound you effectively have only `toString`, `equals`, `hashCode` and must treat the value as nullable. Tightening the bound widens the available API: `<T : Number>` exposes `toInt()`, `toDouble()`, etc.; `<T : CharSequence>` exposes `length`, `get`; `<T : Comparable<T>>` exposes `compareTo` (and thus `<`, `>`). This is the core reason to add a bound — not just to restrict callers, but to let the implementation actually do something with the value. The compiler also uses the bound for return-type and smart-cast reasoning.

code

kotlin · 6 lines
kotlin
fun <T : Appendable> writeTwice(sink: T, s: String): T {
    sink.append(s)      // append() comes from Appendable
    sink.append(s)
    return sink
}
val sb = writeTwice(StringBuilder(), "hi") // T = StringBuilder

go deeper

for a junior

Understands a tighter bound lets you call more methods on T.

for a middle

Explains the body sees T as the bound's API and that Any? exposes almost nothing.

for a senior

Frames choosing the loosest sufficient bound as an API-design decision and uses smart casts to widen capability locally.

for a principal

Weighs bound choice against library evolution, source/binary compatibility, and over-constraining public generic signatures.

## Bound = the lens through which the body sees T When you write `fun <T : Bound> f(x: T)`, the compiler treats `x` as having the static type `Bound`'s API. It cannot assume `x` is any *more specific* type, so the **only** members callable on `x` are: - members declared on `Bound` (and its supertypes), and - extension functions/properties in scope whose receiver matches. ## Implicit `Any?` gives you almost nothing ```kotlin fun <T> f(x: T) { x.toString() // OK (Any member), but null-safe semantics // x.length // ERROR: Any? has no length // x.compareTo(x) // ERROR: Any? has no compareTo } ``` With `Any?` the value may even be null, so member access generally needs `?.`. ## Tightening the bound unlocks API ```kotlin fun <T : CharSequence> middleChar(x: T): Char = x[x.length / 2] // length & get from CharSequence fun <T : Number> half(x: T): Double = x.toDouble() / 2 // toDouble from Number fun <T : Comparable<T>> isSorted(a: T, b: T): Boolean = a <= b // compareTo from Comparable ``` ## Restriction at the call site, capability in the body The bound does two complementary jobs: 1. **Restricts** which type arguments callers may use (`f<Foo>` only if `Foo <: Bound`). 2. **Grants** the body the ability to use `Bound`'s members. Choosing the bound is therefore an API-design decision: pick the *loosest* bound that still gives you the operations you need. ## Interaction with non-null bound `<T : Any>` removes the null caveat: members can be called directly without `?.`, because the value can't be null. But `Any` still only exposes `Any`'s members; if you need `length` you must bound by `CharSequence`, etc. ## Smart casts and `is` checks Inside the body you can still narrow with `is`: ```kotlin fun <T : Any> render(x: T): String = if (x is CharSequence) x.length.toString() else x.toString() ``` The bound sets the baseline; `is` checks add temporary capabilities via smart casts.

  • If you only need toString(), what's the loosest sensible bound?
    No explicit bound (implicit Any?), or <T : Any> if you also want to forbid null; both expose toString since it's an Any member.
  • How can you temporarily use members beyond the bound inside the body?
    Use an `is` type check; a successful smart cast narrows T to the checked type within that branch, exposing its members.

The bound is the job description for T: the body may only ask T to do tasks listed in that description, nothing more specific.

saying these in an interview costs you the question

  • Assuming an unbounded T exposes domain-specific members like length
  • Believing the bound only restricts callers and has no effect on the body
  • Picking an overly tight bound that needlessly excludes valid callers
  • Forgetting Any? requires null-safe access
  • Thinking you must cast T to call bound members

context