Scope Functions Quiz
KOTLIN › Language Idioms
Which scope function returns the context object and exposes it as this?
- let
- also
- apply
- with
Answer: apply
apply returns the receiver itself and references it as this, making it ideal for configuration that returns the configured object.
You want to log a value as a side effect inside a call chain without changing what is passed along. Which is idiomatic?
- let
- also
- run
- with
Answer: also
also returns the same object and exposes it as it, so it performs a side effect (logging) while the chain continues with the unchanged object.
What does this expression return?
- The original str value, since let just forwards it unchanged
- Always null, because safe calls skip the lambda every time
- An Int?: the lambda result, or null if str is null
- A String, because let converts the value back into text
Answer: An Int?: the lambda result, or null if str is null
let returns the lambda result; with ?. the lambda runs only when str is non-null, so n is the length or null otherwise.
Which pair both return the lambda result?
- run and with
- apply and also
- apply and let
- also and run
Answer: run and with
run and with both return the lambda result. apply and also are the two that return the context object instead.
Inside an apply block on a Person, how do you set its age property?
- it.age = 32
- age = 32
- this.it.age = 32
- value.age = 32
Answer: age = 32
apply exposes the object as this, so members can be accessed bare; age = 32 is equivalent to this.age = 32.
Which scope function is a non-extension that takes the object as an argument and exposes it as this?
- let
- also
- apply
- with
Answer: with
with is called as with(obj) { }, takes the object as an argument, uses this as the reference, and returns the lambda result.
Why might you prefer also over apply even when both return the object?
- also exposes the object as it, avoiding shadowing outer this
- also returns the lambda's result whereas apply returns the object
- also may only be applied to nullable receiver types like T?
- apply cannot be chained in a call sequence but also can
Answer: also exposes the object as it, avoiding shadowing outer this
Both return the object, but also uses it (a named/explicit reference) which reads better for side effects that pass the object as an argument and avoids shadowing an outer this.
What distinguishes the non-extension run { } from the extension form obj.run { }?
- It returns the context object rather than the lambda’s result
- It exposes the object as it instead of this in the block
- It has no receiver object and returns the block's last expression
- It can only be used at the top level of a file, not in expressions
Answer: It has no receiver object and returns the block's last expression
Non-extension run takes no object; it runs a block of statements and returns the last expression, which is handy for scoping temporary variables where an expression is required.
You have val config: Config? and want to call several of its methods only when it is non-null, then use the block's computed result. Which is most idiomatic?
- with(config) { ... }
- config?.run { ... }
- config.apply { ... }
- with(config!!) { ... }
Answer: config?.run { ... }
config?.run { } executes only when config is non-null, exposes it as this so methods are called bare, and returns the lambda result; with has no safe-call form and apply would return the object instead of a result.
Apart from null-checks, what is an idiomatic use of let?
- Configuring an object's properties and then returning that same object
- Doing a side effect and then passing the unchanged object down the chain
- Grouping calls on a non-null object that was passed in as an argument
- Mapping the receiver to a new result while keeping it in a small scope
Answer: Mapping the receiver to a new result while keeping it in a small scope
let returns the lambda result, so it suits mapping a value into a different type, and it limits the introduced variable (it) to the block's scope.
What does user.takeIf { it.isActive } evaluate to when isActive is false?
- null
- the user object
- false
- an exception is thrown
Answer: null
takeIf returns the receiver when the predicate is true and null otherwise, so a false predicate yields null (takeUnless is its inverse).
Kotlin's standard scope functions (let, run, with, apply, also) are declared inline. What does that imply?
- Lambda bodies inline at the call site, enabling non-local returns
- They may only be invoked from within other inline functions
- Their lambda bodies are always dispatched onto a background thread
- They cannot capture variables or vals from the enclosing scope
Answer: Lambda bodies inline at the call site, enabling non-local returns
Being inline, the compiler copies the lambda body into the call site, avoiding a runtime lambda allocation and permitting non-local returns from the enclosing function.
What is the value of x?
- 6
- 5
- Unit
- null
Answer: 5
also ignores the lambda's result and returns the context object itself, so x is 5 even though the lambda prints 6.
Inside a nested let, you need to reference the outer block's object as well as the inner one. What is the idiomatic fix?
- Use this@let to reach the shadowed outer receiver object
- Switch the inner block to apply, which exposes it as this
- Name lambda params, e.g. x.let { o -> y.let { i -> ... } }
- It is impossible, because nested let blocks are not allowed
Answer: Name lambda params, e.g. x.let { o -> y.let { i -> ... } }
let exposes the object as the implicit it, which the inner block shadows; naming the lambda parameters makes both objects accessible, and this@let is invalid because let uses it rather than a labeled receiver.