skip to content

Implement takeIf yourself and discuss its design: the inline/contract considerations and when its use harms readability.

level: principalimportance: nice to knowfreq 18%

answer

  1. inline fun <T> T.takeIf(p): T? = if (p(this)) this else null
  2. inline -> no lambda allocation, free at runtime
  3. contract callsInPlace EXACTLY_ONCE enables val init/flow analysis
  4. Selector family: returns receiver-or-null, not a transform
  5. Convention: short single-condition gates only; else if/when

basics

~10 s

takeIf is a tiny inline extension: if the predicate is true return this, else null. It is inline so the lambda doesn't allocate. Overusing it for complex logic hurts clarity, so prefer if/when there.

solid answer

~40 s

A faithful implementation is `inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null`, with `takeUnless` negating the predicate. The stdlib also annotates it with a `callsInPlace(predicate, EXACTLY_ONCE)` contract so the compiler knows the lambda runs exactly once — enabling definite-assignment of `val`s set inside it. `inline` removes the function-object allocation and the call overhead, so it is free at runtime. Design-wise, `takeIf` is a *value selector* that deliberately introduces `T?`, meant to be paired with `?:`/`?.`. Its costs are readability: a non-trivial predicate, several chained gates, or branches producing different shapes all read better as explicit `if`/`when`. As a principal you set the convention: use `takeIf`/`takeUnless` for short, single-condition keep-or-default expressions; switch to control flow when logic grows or when the introduced nullability is incidental rather than intended.

code

kotlin · 11 lines
kotlin
inline fun <T> T.takeIfMine(predicate: (T) -> Boolean): T? =
    if (predicate(this)) this else null

inline fun <T> T.takeUnlessMine(predicate: (T) -> Boolean): T? =
    if (!predicate(this)) this else null

// inline lets a non-local return work from the predicate:
fun firstReady(nodes: List<Node>): Node? {
    nodes.forEach { n -> n.takeIf { it.ready }?.let { return it } }
    return null
}

go deeper

for a junior

Can state it returns this-or-null and is a small helper, even if unsure of inline/contracts.

for a middle

Implements the one-liner correctly and knows it is inline so there's no allocation.

for a senior

Adds the callsInPlace contract, explains the selector vs transformer family, and the readability limits.

for a principal

Frames team conventions for when fluent gating aids vs. harms maintainability, grounds it in the trivial inline implementation, and weighs intent-communication over (identical) runtime cost.

## Implementing it The standard-library definitions are essentially: ```kotlin public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (predicate(this)) this else null } public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (!predicate(this)) this else null } ``` Key ingredients: - **Extension on generic `T`** — callable on any value, including nullable receivers (where `this` may be null). - **`inline`** — the lambda body is copied into the call site. No `Function1` object is allocated, no virtual call; non-local `return` from the predicate is even possible. Effectively zero runtime cost. - **`contract { callsInPlace(predicate, EXACTLY_ONCE) }`** — a *Kotlin contract* telling the compiler the lambda is invoked exactly once. This lets you assign a `val` inside the predicate and have the compiler treat it as definitely initialized afterward, and improves smart-cast/flow analysis. ## takeUnless equivalence `x.takeUnless { p }` is exactly `x.takeIf { !p }`. Providing both lets the predicate stay positive and avoids double negatives in reader-facing code. ## Design intent `takeIf`/`takeUnless` are **selectors** in the scope-function family. Unlike `let`/`run` (transformers) and `also`/`apply` (side-effecting, return receiver), selectors return the **receiver or null**. They exist to convert a value-producing `if/else` into a fluent expression, especially when chained with the **Elvis operator `?:`**. ## When it harms readability - **Complex predicates.** A multi-line or side-effecting predicate buried in `takeIf { ... }` is harder to scan than an `if` block. - **Stacked gates.** `a.takeIf{}?.takeUnless{}?.takeIf{}` layers nullability and obscures the actual condition; collapse into one predicate or use `when`. - **Incidental nullability.** If you don't actually want a `null` outcome, introducing one just to use `takeIf` is a smell. - **Different result shapes.** When the two branches produce different things, `if`/`when` makes the divergence explicit; `takeIf` only ever yields the receiver-or-null. ## Governance angle As a principal/lead the value is in **codifying the convention**: encourage `takeIf`/`takeUnless` for terse single-condition keep-or-default expressions; require explicit control flow once predicates grow, several gates stack, or reviewers stumble. The runtime is identical either way (`inline`), so the decision is purely about communicating intent. ## Bonus: relation to standard if Because `takeIf` is just `if (predicate(this)) this else null`, there is nothing magic — it is sugar. Knowing this demystifies it and helps reason about edge cases (e.g., the predicate sees the possibly-null `this` when called on a nullable receiver without `?.`).

  • What does the callsInPlace(EXACTLY_ONCE) contract buy the caller?
    It tells the compiler the predicate runs exactly once, so a val assigned inside it is treated as definitely initialized afterward and flow/smart-cast analysis works through the call.
  • Why is takeIf essentially free at runtime?
    It's inline, so the lambda is inlined at the call site with no Function object allocation and no extra call — it compiles to a plain if-expression.

saying these in an interview costs you the question

  • Thinking takeIf is a special compiler intrinsic rather than a one-line inline function
  • Believing it allocates a lambda object each call
  • Defending deeply chained takeIf as always cleaner than when
  • Introducing null just to use takeIf when no null outcome is wanted
  • Not knowing takeUnless is just takeIf with a negated predicate

context