How is also used for side-effecting and logging inside a call chain, and why is it a better fit than apply for that job?
answer
- also = side effects without breaking the chain
- it argument, returns the same object
- Logging, validation, caching, registering
- Renamable param: .also { user -> ... }
- Configuration -> apply; peek -> also
basics
~20 salso lets you slip a side effect — like logging, validating, or saving — into a chain without breaking it. It hands the object back unchanged, and you read it as it, which keeps logs and effects clear.
solid answer
~40 salso is the idiomatic scope function for side effects that don't transform the value: logging, validation/assertions, debugging printouts, caching, or registering the object somewhere. It exposes the object as it (a normal argument), and returns the same object, so you can drop it into the middle of a chain: produce().also { log.debug("got $it") }.process(). The explicit it name (or a renamed parameter like .also { user -> ... }) signals 'this is a peek, not a mutation' and reads better than apply's implicit this for code that isn't configuring the object. Because it returns the receiver, the surrounding chain is untouched. Apply could technically log too, but its this receiver suggests configuration and can collide with member names, so also communicates intent more clearly for pure side effects.
code
kotlin · 5 linesfun loadUser(id: Long): UserDto =
repo.findById(id)
.also { log.info("loaded user {}", it.id) }
.also { require(it.active) { "inactive user" } }
.let { it.toDto() }go deeper
Knows also runs a side effect and returns the object as it.
Picks also over let for logging because also preserves the chain value, and over apply because it signals a peek not a mutation.
Uses named parameters and validation in also, and reasons about name-collision avoidance vs apply.
Establishes conventions for keeping value pipelines readable, isolating side effects, and avoiding overuse that hides control flow.
## What 'side-effecting' means here A *side effect* is work that affects the outside world (or another object) but does **not** change the value flowing through your expression — logging, metrics, validation, persisting, adding to a collection. `also` is built for exactly this. ## The shape of also `fun <T> T.also(block: (T) -> Unit): T` — the object is passed as the argument **`it`**, and the **same object is returned**. So `also` is transparent to a chain: whatever went in comes out. ```kotlin val result = repository.find(id) .also { log.debug("loaded $it") } // side effect, value unchanged .also { require(it.isValid) } // validation .let { it.toDto() } // now transform ``` ## Why also beats apply for side effects - **Intent signaling.** `it` reads as "here's the object, do something with it on the side," whereas `apply`'s implicit `this` reads as "configure me." Choosing `also` tells the reader the block is a *peek*, not a mutation. - **No name collisions.** With `apply`, an unqualified `name` could mean a member of the object; with `also` you always write `it.name`, so there's no ambiguity about whether you're touching the receiver or an outer variable. - **Renameable parameter.** You can name the argument for clarity: `.also { user -> audit.record(user) }`, which self-documents long chains. ## Common patterns ```kotlin // Debug-peek in a pipeline val clean = input.trim().also { println("trimmed='$it'") }.lowercase() // Register on creation val conn = Connection().also { openConnections += it } // Side-effecting initialization while keeping a val val bus = EventBus().also { it.register(this) } ``` ## Boundaries (don't drift) - If the block **mutates the object's own properties** (configuration), `apply` is the cleaner choice. - If you need the block's **result** rather than the object, that's `let`/`run`, not `also`. ## A note on purity Using `also` for logging keeps the *value pipeline* pure-looking while isolating impurity in clearly marked blocks — a readability win, though the program is of course still doing side effects.
- Could apply replace also for `.also { log.debug("$it") }`?Functionally yes (both return the receiver), but apply uses this, so it reads as configuration and risks member-name collisions; also's it states 'side effect' clearly.
- Why not use let for logging?let returns the lambda's last expression. If your last line is a log call returning Unit, the chain value becomes Unit. also always returns the object, so the chain is preserved.
also is a tollbooth on a highway: the car (object) stops, you note its plate (log/validate), then it drives on unchanged.
saying these in an interview costs you the question
- Using let for a side effect and accidentally returning Unit/the log result
- Mutating the object inside also (that's apply's job)
- Claiming also transforms the value
- Putting business logic that must affect downstream value inside a side-effect block