You need a function that returns the single element of a collection or throws. Would you write a generic function, and how does using a type parameter compare to writing per-type overloads or returning Any?
answer
- One fun <T> beats N per-type overloads (DRY, open set)
- Return T, not Any → no caller downcasts, no CCE
- Inference keeps call sites clean: single(users)
- Mirrors stdlib Iterable<T>.single()
- Plain generic suffices unless body needs runtime T
basics
~10 sYes, write one generic function fun <T> single(c: Collection<T>): T. It works for every element type, returns the exact type, and avoids copy-pasting a version per type or returning Any and forcing casts.
solid answer
~50 sA single generic function — fun <T> single(c: Collection<T>): T — is the right tool: one definition serves every element type, and the return type is precisely T, so callers get back the concrete type with no cast. The alternatives are worse. Per-type overloads (singleInt, singleString, …) duplicate logic, can't cover user types, and bloat the API. Returning Any (fun single(c: Collection<Any>): Any) erases the caller's type information, forcing an unsafe downcast at every call site and losing compile-time safety. The generic version preserves variance through the call: pass a List<User>, get a User. Type-argument inference means callers write single(users) with no ceremony. The kotlin-stdlib does exactly this (e.g. Iterable<T>.single()). The only time you'd avoid a plain generic is when the body needs runtime knowledge of T (then reified/inline or a type token), which isn't the case here.
code
kotlin · 11 lines// Generic: one definition, exact return type, no casts
fun <T> single(c: Collection<T>): T {
require(c.size == 1) { "Expected exactly one element, got ${c.size}" }
return c.first()
}
val name: String = single(listOf("only")) // T = String, inferred
// Anti-pattern: returning Any forces an unsafe downcast everywhere
fun singleAny(c: Collection<Any>): Any = c.first()
val s = singleAny(listOf("only")) as String // ClassCastException riskgo deeper
Picks the generic function and knows it returns the element type without a cast.
Contrasts the generic with overloads and Any, citing DRY and the downcast/ClassCastException risk of Any.
Articulates why the type flows argument→result, ties it to stdlib design, and knows the reified/token boundary where a plain generic stops being enough.
Frames it as an API-design principle (preserve caller types, minimize surface, avoid unsafe casts) and judges when inline/reified or tokens are justified versus over-engineering.
## The design choice The task — return the one element or throw — is type-agnostic in its **logic** but must preserve the **element type** for the caller. That is exactly what a generic function provides. ```kotlin fun <T> single(c: Collection<T>): T { require(c.size == 1) { "Expected exactly one element, got ${c.size}" } return c.first() } val u: User = single(listOf(User("a"))) // T inferred = User, no cast ``` ## Why not per-type overloads? ```kotlin fun singleInt(c: Collection<Int>): Int { ... } fun singleString(c: Collection<String>): String { ... } ``` - **Duplication**: identical body copied N times. - **Open set**: you can never cover user-defined or future types — `User`, `Order`, etc. - **API bloat**: N functions instead of one. - **Maintenance**: a fix must be applied everywhere. ## Why not return `Any`? ```kotlin fun single(c: Collection<Any>): Any { ... } val u = single(users) as User // unsafe cast at EVERY call site ``` - The caller's element type is **discarded**; the result is `Any`. - Every call needs a **downcast**, which can throw `ClassCastException`. - You lose **compile-time** guarantees — the whole point of static typing. - It can't even accept a `List<User>` cleanly without variance gymnastics. ## Why the generic wins - **One definition, all types** — including types that don't exist yet. - **Exact return type** — `T` flows from argument to result; `single(users): User`. - **No casts** — inference + precise typing keep call sites clean: `single(users)`. - **Matches stdlib** — `Iterable<T>.single()` is built this way; idiomatic Kotlin reaches for a generic extension here. ## When a plain generic is NOT enough If the body needed to **inspect or construct** `T` at runtime (reflection, `is T`, `T()`), erasure would block it and you'd need `inline fun <reified T>` or a `KClass<T>` token. The `single` logic needs none of that — it only moves a `T` value through — so a plain `fun <T>` is ideal. Choosing a generic function over overloads or `Any` is the canonical example of using type parameters to keep code DRY and type-safe simultaneously.
- What concrete safety problem does returning Any introduce that the generic version avoids?Any discards the element type, so every call site must downcast (e.g. as User), which can throw ClassCastException at runtime; the generic returns the exact type T with no cast and full compile-time checking.
- When would a plain fun <T> be insufficient for a problem like this?When the body must introspect or construct T at runtime (is T, T(), T::class). Then you need inline fun <reified T> or to pass a KClass<T>/Class<T> token — but single() needs none of that.
saying these in an interview costs you the question
- Recommending per-type overloads for type-agnostic logic
- Returning Any and downcasting at call sites
- Claiming a generic needs reified here (it doesn't — value just flows through)
- Thinking overloads can cover arbitrary user types
- Not recognizing the stdlib single() as the same pattern