skip to content

Implement both `compose` and `andThen` as infix extensions on function types and explain why the generic signatures differ in argument order.

level: middleimportance: must knowfreq 45%

answer

  1. andThen receiver = (A)->B (input side); compose receiver = (B)->C (output side)
  2. a andThen b == b compose a
  3. Intermediate type B is the contact point both share
  4. Associative, not commutative
  5. compose mirrors math (f ∘ g)(x) = f(g(x))

basics

~10 s

andThen runs the receiver first; compose runs the argument first. Their type parameters differ because the receiver and argument swap which function produces the intermediate value the other consumes.

solid answer

~40 s

Both are `infix` extensions returning a new lambda. `andThen` reads left-to-right: the receiver `(A)->B` runs first, the argument `(B)->C` consumes its output, result `(A)->C`. `compose` follows the math convention `(f ∘ g)(x) = f(g(x))`: the receiver `(B)->C` runs last, the argument `(A)->B` runs first, result `(A)->C`. The signatures differ because in `andThen` the receiver is the *input-side* function (`(A)->B`), while in `compose` the receiver is the *output-side* function (`(B)->C`). The intermediate type B is always wired so one function's output matches the other's input; the compiler enforces this. Both bodies just capture the two functions in a closure and defer execution until invoked.

code

kotlin · 7 lines
kotlin
infix fun <A, B, C> ((A) -> B).andThen(g: (B) -> C): (A) -> C = { g(this(it)) }
infix fun <A, B, C> ((B) -> C).compose(g: (A) -> B): (A) -> C = { this(g(it)) }

val inc: (Int) -> Int = { it + 1 }
val dbl: (Int) -> Int = { it * 2 }
println((inc andThen dbl)(3)) // (3+1)*2 = 8
println((inc compose dbl)(3)) // (3*2)+1 = 7

go deeper

for a junior

Can write andThen; may need help articulating why compose's receiver type differs.

for a middle

Writes both correctly, explains the receiver-side difference and the a andThen b == b compose a identity.

for a senior

Adds associativity/non-commutativity, discusses readability trade-offs, and the type-safety role of the generics.

for a principal

Relates these to standard FP laws and decides team conventions (prefer andThen pipelines) for maintainability.

## The two directions There are two standard ways to glue `f` and `g` together, and they differ only in *reading order*: | Helper | Receiver runs | Result `(x)` | |---|---|---| | `andThen` | first | `g(f(x))` | | `compose` | last | `f(g(x))` | ## The implementations ```kotlin infix fun <A, B, C> ((A) -> B).andThen(g: (B) -> C): (A) -> C = { x -> g(this(x)) } // this = (A)->B runs first infix fun <A, B, C> ((B) -> C).compose(g: (A) -> B): (A) -> C = { x -> this(g(x)) } // this = (B)->C runs last ``` ## Why the type parameters swap roles The **receiver** (`this`) is what you call the helper *on*. The trick is which side of the data flow the receiver sits on: - In **`andThen`**, the receiver is the **first/input-side** function, so it must accept `A` and produce the intermediate `B`: receiver type `(A) -> B`. The argument then takes `B` to `C`. - In **`compose`**, the receiver is the **last/output-side** function, so it must accept the intermediate `B` and produce `C`: receiver type `(B) -> C`. The argument takes `A` to the intermediate `B`. In both cases the **intermediate type `B`** is the contact point: one function's return type must equal the other's parameter type. The generics force this — pass mismatched functions and it won't compile. This is the safety payoff of threading `A, B, C`. ## Equivalences ```kotlin // These produce identical behavior: val p1 = f andThen g // g(f(x)) val p2 = g compose f // g(f(x)) ``` So `a andThen b == b compose a`. Pick whichever reads better. Most Kotlin code prefers `andThen` because left-to-right matches how data flows in a pipeline. `compose` exists for parity with mathematical/functional notation. ## Gotchas - Function composition is **associative**: `(f andThen g) andThen h == f andThen (g andThen h)`. - It is **not commutative**: order matters. - Both are lazy — they build a closure and run nothing until called.

  • Why does the receiver type differ between andThen and compose?
    Because the receiver sits on opposite ends of the pipeline: in andThen it's the first function (A)->B; in compose it's the last function (B)->C.
  • Is `f andThen g` ever equal to `g compose f`?
    Always, by definition. Both evaluate to g(f(x)). They're just two notations for the same composition.

saying these in an interview costs you the question

  • Gives both helpers the same receiver type, missing why they differ
  • Claims composition is commutative
  • Cannot explain that B is the shared intermediate type
  • Writes compose as g(f(x)) (that's andThen's behavior)
  • Says the generics are optional decoration rather than the source of type safety

context