Sitemap

Knowing Kotlin is one thing — using it effectively is another.

5 Kotlin Patterns I Wish I Knew Before Building Real Projects

From sealed classes to extension magic—these Kotlin patterns transformed how I build and maintain Android apps.

--

Press enter or click to view image in full size
Photo by Kate Branch on Unsplash

Not a Medium Member? “Read For Free”

When I first started using Kotlin in Android projects, I was blown away by the syntax. val and var, smart casts, null-safety—what’s not to love?

But the more real-world projects I built, the more I realized something: writing Kotlin and writing idiomatic Kotlin are two very different things.

In this post, I’m sharing 5 Kotlin patterns that I wish I had known earlier — patterns that would’ve made my code cleaner, safer, and easier to scale. Whether you’re new to Kotlin or you’re already shipping Compose-based apps, these patterns will level up your Kotlin game.

1. Sealed Classes + When = The Best State Handling Duo

Sealed classes are Kotlin’s answer to exhaustive type hierarchies — and they shine when representing UI states, network results, or events.

sealed class UiState {
object Loading : UiState()
data class Success(val data: List<User>) : UiState()
data class Error(val message: String) : UiState()
}

In your ViewModel:

val uiState: StateFlow<UiState> = MutableStateFlow(UiState.Loading)

In your Composable:

when (val state = viewModel.uiState.collectAsState().value) {
is UiState.Loading -> LoadingScreen()
is UiState.Success -> UserList(state.data)
is UiState.Error -> ErrorScreen(state.message)
}

Why It’s Powerful:

  • Compiler forces you to handle all cases
  • Better than enums for passing data
  • Clean separation of concerns

2. Extension Functions: Less Boilerplate, More Power

Extension functions let you “add” methods to existing classes, including ones you don’t own.

fun Long.toFormattedDate(): String {
val sdf = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
return sdf.format(Date(this))
}

Now:

user.createdAt.toFormattedDate()

Why It’s Powerful:

  • Makes utility code cleaner
  • Encourages Kotlin’s expressive style
  • Helps isolate logic without bloating classes

3. let, apply, also, run, with: The Kotlin Scope Functions

At first, they’re confusing. Then you realize — they’re magic.

Each scope function solves different problems depending on what you return or operate on.

val name: String? = getUserName()
name?.let {
println("User is $it")
}

Why It’s Powerful:

  • Reduces null-check boilerplate
  • Encourages functional patterns
  • Makes builders & chained calls more readable

Tip: Learn the difference once and your code becomes 10Ă— cleaner.

4. Data Class Copy Pattern: Small Change, No Problem

Data classes are known for equals, toString, and destructuring—but don’t overlook the copy() function.

data class User(val id: Int, val name: String, val isActive: Boolean)

val updatedUser = user.copy(isActive = true)

Instead of mutating the object, you create a new one with just the changed field.

Why It’s Powerful:

  • Encourages immutability
  • Great for StateFlow or Redux-like architectures
  • Makes bugs less likely in state updates

5. Result Wrappers: Goodbye Try-Catch Hell

Wrap your API or database calls inside a result wrapper that handles both success and failure elegantly.

sealed class Result<out T> {
data class Success<T>(val data: T): Result<T>()
data class Failure(val throwable: Throwable): Result<Nothing>()
}

Your repository:

suspend fun getUsers(): Result<List<User>> = try {
val response = api.fetchUsers()
Result.Success(response)
} catch (e: Exception) {
Result.Failure(e)
}

Now you can handle it like:

when (val result = repository.getUsers()) {
is Result.Success -> showUsers(result.data)
is Result.Failure -> showError(result.throwable.message)
}

Why It’s Powerful:

  • Centralizes error handling
  • Plays well with sealed classes
  • Keeps try-catch out of UI layers

Conclusion

Kotlin is more than just a “better Java” — it’s a toolbox full of expressive, safe, and functional features. The patterns above aren’t advanced tricks — they’re idiomatic tools that make your codebase stronger, faster, and easier to read.

If you’re serious about building maintainable, modern Android apps (especially with Jetpack Compose), investing time in learning these Kotlin patterns will pay off immediately.

--

--

Jayant Kumar🇮🇳
Jayant Kumar🇮🇳

Written by Jayant Kumar🇮🇳

Jayant Kumar is a Lead Software Engineer, passionate about building modern Android applications. He shares his expertise in Kotlin, Android, and Compose.

No responses yet