skip to content

Implement a generic extension fun <T> Iterable<T>.firstMatchingOrNull(predicate: (T) -> Boolean): T?. Explain why the return is T? and how it behaves when T is itself a nullable type.

level: middleimportance: should knowfreq 45%

answer

  1. Loop, return on match, else null
  2. T? encodes 'not found'
  3. Unbounded T => Any?, may be nullable
  4. null match vs no-match ambiguity
  5. Fix with <T : Any> or index/sealed result

basics

~20 s

Loop over the elements, return the first that matches, else null. The return is T? because there may be no match. If T is nullable, T? is still just that nullable type, so a matched null is indistinguishable from 'no match'.

solid answer

~40 s

You write fun <T> Iterable<T>.firstMatchingOrNull(predicate: (T) -> Boolean): T? { for (e in this) if (predicate(e)) return e; return null }. The return is T? because the function may find nothing and must signal absence with null. With an unbounded <T> (bound Any?), T may already be nullable; since Kotlin flattens nullability, T? equals T when T is nullable, so a list of String? where the matching element is null returns null — identical to the 'not found' result. That ambiguity is exactly why the stdlib's firstOrNull has the same caveat, and why a sentinel or index-based search is preferred when elements can legitimately be null. Constraining to <T : Any> would remove the ambiguity by forbidding null elements entirely.

code

kotlin · 7 lines
kotlin
fun <T> Iterable<T>.firstMatchingOrNull(predicate: (T) -> Boolean): T? {
    for (element in this) if (predicate(element)) return element
    return null
}

val xs: List<String?> = listOf(null, "a")
val r = xs.firstMatchingOrNull { it == null } // returns null: match or miss? ambiguous

go deeper

for a junior

Writes the loop and returns null on no match; uses T? for the return.

for a middle

Explains the nullable-element ambiguity and that T? flattens to T when T is nullable.

for a senior

Proposes index- or sealed-wrapper alternatives and ties it to firstOrNull's known caveat.

for a principal

Weighs API-contract tradeoffs (null vs Optional vs sentinel) for library-grade search functions.

## The implementation ```kotlin fun <T> Iterable<T>.firstMatchingOrNull(predicate: (T) -> Boolean): T? { for (element in this) { if (predicate(element)) return element } return null } ``` - The type parameter `T` is **unbounded**, so its upper bound is `Any?` and `T` may be a nullable type. - The return type is `T?` because the search may fail and we use `null` to mean **'no element matched'**. ## Why `T?` and not `T` A total function over a possibly-empty iterable cannot always return an element. `T?` lets us encode *absence* in the type, forcing the caller to handle the miss: ```kotlin val found: String? = listOf("a", "bb").firstMatchingOrNull { it.length == 2 } ``` ## The nullable-element ambiguity Because `<T>` is `Any?`-bounded, `T` itself can be `String?`. Kotlin **flattens** nullability, so `T?` is the *same type* as `T` when `T` is already nullable (there is no `String??`). That creates an ambiguity: ```kotlin val xs: List<String?> = listOf(null, "a") val r = xs.firstMatchingOrNull { it == null } // returns null (a real match!) // but null is also what 'no match' returns — indistinguishable ``` A returned `null` could mean *'the matching element was null'* **or** *'nothing matched'*. You cannot tell them apart. ## Resolving the ambiguity Options: - **Constrain `<T : Any>`** so elements can't be null; then `null` unambiguously means 'no match'. - **Return an index** (`indexOfFirst` returns `-1` for 'not found') so a null element is still locatable. - **Return a sealed/`Result`-like wrapper** distinguishing `Found(value)` from `NotFound`. ## Relation to the standard library This mirrors `Iterable<T>.firstOrNull`, which has exactly the same nullable-element caveat. Knowing it shows you understand why `null`-returning generic APIs are sometimes insufficient.

  • How would you make 'null element matched' distinguishable from 'no match'?
    Return an index (like indexOfFirst, -1 = none) or a sealed wrapper (Found/NotFound) instead of T?.
  • What does constraining to <T : Any> buy you here?
    Elements can no longer be null, so a returned null unambiguously means 'nothing matched'.

saying these in an interview costs you the question

  • Returning T instead of T? and throwing on empty
  • Not recognizing the null-match/no-match ambiguity
  • Claiming T? becomes String?? when T is String?
  • Forgetting the unbounded T can already be nullable
  • Using !! to force the result non-null

context