How do you build a readable multi-step pipeline by composing function values, and how do method references (`::`) and bound references fit in?
answer
- Pipeline = single composed function via andThen chain
- :: makes function values: unbound (receiver first), bound (captures instance), constructor
- Reads in execution order vs inside-out nesting
- Lambdas and references both compose freely
- Compiler enforces output->input wiring per link
basics
~10 sChain steps with andThen to form one pipeline function. You can plug in lambdas or method references like String::trim or obj::save so each step reads as a named operation.
solid answer
~40 sA pipeline is a single composed function: `val process = parse andThen normalize andThen validate andThen persist`. Each operand is a function value of type `(A)->B`. Method references (`::`) turn existing functions into values: `String::trim` (unbound, takes the receiver as first arg), `repo::save` (bound, captures `repo`), or `::topLevelFn`. They slot directly into composition because their type is `(In)->Out`. Composition stays lazy, so `process` is just a recipe until invoked. Compared to nesting `persist(validate(normalize(parse(x))))`, the `andThen` chain reads top-to-bottom in execution order and names each stage. Watch the wiring: each step's output type must match the next step's input type, which the generic helper enforces at compile time.
code
kotlin · 5 linesinfix fun <A, B, C> ((A) -> B).andThen(g: (B) -> C): (A) -> C = { g(this(it)) }
fun parse(s: String): List<String> = s.split(",")
val pipeline = ::parse andThen List<String>::size andThen { "count=$it" }
println(pipeline("a,b,c")) // count=3go deeper
Can chain two steps with andThen and use a simple lambda or ::name reference.
Builds multi-step pipelines, knows unbound vs bound references and their types, mixes lambdas and references.
Argues readability and type-safety benefits, and knows when sequence operators are the better composition tool.
Sets guidance on where named-pipeline composition pays off vs over-engineering, considering testability and reuse.
## Building the pipeline Composition shines when you have several single-purpose functions and want one combined operation. Using the `andThen` helper: ```kotlin val process: (String) -> Report = ::parse andThen ::normalize andThen ::validate andThen ::render val report = process(rawInput) ``` This reads in **execution order** (left to right), unlike nested calls `render(validate(normalize(parse(raw))))`, which read inside-out. ## Method references (`::`) as function values The `::` operator produces a **callable reference** — a function value you can compose: - **Top-level / local**: `::parse` → type `(String) -> Parsed`. - **Unbound class member**: `String::trim` → type `(String) -> String`; the receiver becomes the **first parameter**. So `String::length` is `(String) -> Int`. - **Bound reference**: `repo::save` captures a specific `repo` instance → type `(Entity) -> Unit`. The instance is fixed when the reference is created. - **Constructor reference**: `::User` → a function returning a `User`. Because each of these *is* a `(In) -> Out` value, they drop straight into `andThen`/`compose` with no wrapper lambda. ```kotlin val trimmedLength = String::trim andThen String::length // (String) -> Int ``` ## Mixing lambdas and references ```kotlin val clean: (String) -> String = { it.lowercase() } val pipeline = clean andThen String::trim andThen { it.take(10) } ``` Lambdas and references interoperate freely since both are function values. ## Type wiring Every `andThen` link demands output-of-step-N == input-of-step-N+1. The generic helper threads these types, so a mismatched stage is a **compile error**, not a runtime surprise. This is the main reliability benefit over manual nesting where a wrong order may still type-check by accident. ## When to prefer this - Reusable, named, branch-free pipelines (parse → validate → transform). - When you want to pass the whole pipeline around as one value. ## When NOT to - If you have a collection, prefer `Sequence`/`Iterable` operators (`map`, `filter`) which already compose lazily. - For one-off two-step logic, a direct call is clearer than building helpers.
- What is the type of an unbound reference like `String::length`?`(String) -> Int`. The receiver becomes the first parameter, so it composes like any (String)->Int function.
- When should you reach for collection operators instead of building an andThen chain?When the data is a collection/sequence; map/filter/etc. already compose lazily and read better than wrapping element transforms in andThen.
Method references are like pre-labeled hoses; andThen just clamps them end to end into one pipe.
saying these in an interview costs you the question
- Thinks an unbound member reference's receiver is hidden rather than the first parameter
- Cannot distinguish bound vs unbound references
- Builds pipelines for collection element work where map/filter would be clearer
- Claims method references can't be composed because they're 'not lambdas'
- Ignores that each link's types must align