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.
answer
- Loop, return on match, else null
- T? encodes 'not found'
- Unbounded T => Any?, may be nullable
- null match vs no-match ambiguity
- Fix with <T : Any> or index/sealed result
basics
~20 sLoop 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 sYou 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 linesfun <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? ambiguousgo deeper
Writes the loop and returns null on no match; uses T? for the return.
Explains the nullable-element ambiguity and that T? flattens to T when T is nullable.
Proposes index- or sealed-wrapper alternatives and ties it to firstOrNull's known caveat.
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