Sitemap
Himanshugaur

Himanshugaur

Android dev for 6+ years. Survived SDK migrations, wrestled with RecyclerView. Sharing practical tips, deep dives & honest takes on building great Android apps.

KOTLIN Scope Functions: You’re Using Them Wrong Every Day

7 min readJun 25, 2026

--

Press enter or click to view image in full size

First, Why Should You Even Care?

Most Android devs learn one scope function, get comfortable, and use it everywhere. The code works. No crash. No warning. So they never question it.

But using the wrong scope function makes your intent unclear to anyone reading the code, including future you. Clean code is about communication, not just correctness.

There are 5 scope functions: let, apply, also, run, with

Today, we focus on the 3 most misused: apply, also, let

The Two Things That Separate Them

Before examples, burn these two questions into your brain:

Press enter or click to view image in full size

That’s it. Everything else follows from these two.

1. apply — "Set me up"

Access object as: this (so you can skip writing this.)
Returns: The object itself

Think of it as: “Apply these configurations to me.”

// WITHOUT apply
val button = Button(context)
button.text = "Submit"
button.textSize = 16f
button.isEnabled = true
button.setBackgroundColor(Color.BLUE)

// WITH apply
val button = Button(context).apply {
text = "Submit" // this.text — 'this' is the Button
textSize = 16f
isEnabled = true
setBackgroundColor(Color.BLUE)
}

Clean. Readable. The intent is clear: I am configuring this object.

Real Android Example

// Building an Intent
val intent = Intent(context, LoginActivity::class.java).apply {
putExtra("userId", 123)
putExtra("source", "home_screen")
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
// Setting up a RecyclerView
recyclerView.apply {
layoutManager = LinearLayoutManager(context)
adapter = myAdapter
setHasFixedSize(true)
addItemDecoration(DividerItemDecoration(context, VERTICAL))
}

When to use apply

  • Object initialization and configuration
  • Especially when you’re setting multiple properties at once
  • When you don’t need a return value from the block

2. also — "Do this too, but don't touch me"

Access object as: it
Returns: The object itself

Think of it as: “Also do this side effect, but I stay unchanged.”

The keyword also is literally the English word. Read it out loud:

“Create this user… also log it… also send analytics.”

// WITHOUT also
val user = createUser()
Log.d("TAG", "User created: $user")
saveToDatabase(user)
// now use user...

// WITH also
val user = createUser()
.also { Log.d("TAG", "User created: $it") }
.also { saveToDatabase(it) }

Real Android Example

// Logging without breaking a chain
fun getToken(): String {
return sharedPrefs
.getString("auth_token", "")
.also { Log.d("Auth", "Token fetched: $it") } // side effect
}
// Sending analytics while building an object
val product = Product(id = "123", name = "Shoes")
.also { analyticsManager.trackProductCreated(it.id) }
.also { cache.store(it) }

The KEY difference from apply

// apply — 'this' is the object, feels like you're INSIDE it
product.apply {
name = "Shoes" // direct property access
price = 99.0
}

// also — 'it' is the object, you're OUTSIDE looking in
product.also {
it.name = "Shoes" // must use 'it.'
it.price = 99.0
}

Using also to configure properties is technically valid but semantically wrong. You're saying "do this side effect" but you're actually configuring it. Use apply for that.

When to use also

  • Logging in a chain
  • Debugging without breaking a chain
  • Side effects like analytics, caching, saving to DB
  • When you want to say “do this extra thing, but it doesn’t change the main flow”

3. let — "Transform me or work with me safely"

Access object as: it
Returns: The last expression (whatever you return from the block)

Get Himanshugaur’s stories in your inbox

Join Medium for free to get updates from this writer.

Think of it as: “Take this value, do something with it, give me back something new.”

Most Common Use: Null Safety

This is where 90% of devs first meet let, and it's the most valid use case:

// Without let — ugly and error-prone
val name: String? = getUserName()
if (name != null) {
textView.text = name.uppercase()
showGreeting(name)
}

// With let — clean null check
getUserName()?.let { name ->
textView.text = name.uppercase()
showGreeting(name)
}

The ?.let pattern is idiomatic Kotlin. If the value is null, the block doesn't execute. Beautiful.

Second Use: Transformation

// let returns the last expression — use this for transformation
val greeting = userName
.let { it.trim() }
.let { "Hello, $it!" }
.let { it.uppercase() }

// Result: "HELLO, JOHN!"
// Real example — transform API response to UI model
apiResponse
.let { mapToUiModel(it) } // returns UiModel
.let { renderOnScreen(it) } // returns Unit

Real Android Example

// Only show dialog if fragment is still attached
activity?.let { safeActivity ->
AlertDialog.Builder(safeActivity)
.setTitle("Error")
.setMessage("Something went wrong")
.show()
}
// Transforming data in a ViewModel
fun getDisplayName(): String {
return user
?.let { "${it.firstName} ${it.lastName}" }
?: "Guest"
}

When to use let

  • Null safety with ?.let
  • Transforming a value into something else
  • When you need the result of the block
  • Limiting the scope of a variable (avoid polluting outer scope)

Side-by-Side Comparison

val person = Person()

// apply — configuring the object, returns person
person.apply {
name = "John" // 'this.name'
age = 25
} // returns Person

// also — side effect, returns person
person.also {
println(it.name) // 'it.name'
logToAnalytics(it)
} // returns Person

// let — transformation, returns whatever block returns
val greeting = person.let {
"Hello, ${it.name}" // returns String
} // returns String, not Person

The Mistake You’re Making Right Now

Mistake 1: Using let instead of apply for configuration

// WRONG — let doesn't communicate "I'm configuring this"
val textView = TextView(context).let {
it.text = "Hello"
it.textSize = 16f
it // have to return it manually!
}

// RIGHT
val textView = TextView(context).apply {
text = "Hello"
textSize = 16f
}

Notice with let you have to write it at the end to return the object. That's a code smell you're fighting the function.

Mistake 2: Using apply for side effects

// WRONG — apply is not meant for side effects
user.apply {
Log.d("TAG", "User: $this") // feels weird
database.save(this)
}

// RIGHT
user.also {
Log.d("TAG", "User: $it")
database.save(it)
}

Mistake 3: Using also when you need transformation

// WRONG — also always returns the original object
val greeting = user.also {
"Hello ${it.name}" // this line does NOTHING
}
// greeting is still a User object, not a String!

// RIGHT
val greeting = user.let {
"Hello ${it.name}" // let returns this
}
// greeting is "Hello John"

This is a silent bug. No error. No warning. The string is just thrown away.

Quick Decision Chart

Do you need the result of the block?
│
├── YES → use let
│ (transformation, null safety)
│
└── NO (you want the original object back)
│
├── Are you CONFIGURING properties?
│ └── YES → use apply (this, returns object)
│
└── Are you doing SIDE EFFECTS?
└── YES → use also (it, returns object)

Memory Trick

Press enter or click to view image in full size

One Final Real-World Example

// All three working together — notice how readable this is
val userCard = UserCard()
.apply { // configure it
name = currentUser.fullName
avatarUrl = currentUser.photo
isPremium = currentUser.hasPlan
}
.also { // side effect
analyticsManager.trackCardCreated(it.name)
Log.d("UI", "Card ready: ${it.name}")
}
.let { // transform to view state
UiState.Success(it) // returns UiState, not UserCard
}

Each function is doing exactly what it’s named for. Any dev reading this instantly knows what’s happening at each step.

TL;DR

  • apply → configure an object (this, returns object)
  • also → side effects like logging/analytics (it, returns object)
  • let → null safety or transformation (it, returns block result)

The code will “work” either way. But using the right one tells the next developer and your future self exactly what you meant.

👋 Let’s Connect

If you enjoy content about Android development, Jetpack Compose, Kotlin, software architecture, and engineering best practices, I’d love to stay connected.

📺 YouTube
In-depth Android tutorials, real-world projects, architecture discussions, and practical development tips.

💼 LinkedIn
Professional updates, technical insights, articles, and lessons from my software engineering journey.

✍️ Medium
More deep dives into Android development, clean architecture, testing, performance optimization, and modern engineering practices.

📚 Want to Go Deeper?

If you’re looking for structured, comprehensive learning resources, check out my Android development books, where I cover concepts in much greater depth than a typical article.

☕ Support My Work

Creating free technical content, books, tutorials, and open educational resources takes considerable time and effort. If this article helped you learn something valuable, you can support my work by:

• Purchasing one of my books
• Sharing my articles with others
• Buying me a coffee

Every bit of support helps me continue creating high-quality content for the Android community.

Press enter or click to view image in full size

Click here to support

SOLID Principles for Android Developers — Learn how to write maintainable, scalable, and testable Android applications using SOLID principles with real-world examples.

👉 Explore all my books: Book Link

--

--

Written by Himanshugaur

Android dev for 6+ years. Survived SDK migrations, wrestled with RecyclerView. Sharing practical tips, deep dives & honest takes on building great Android apps.