Top Mistakes of Beginner Kotlin Developers: Advanced Level
Kotlin is a modern and concise language that is actively replacing Java in Android development and backend. However, its flexibility often plays a cruel trick on beginners who switch to it from other languages. In this article, we will analyze non-obvious but critical mistakes made by developers who have already mastered the basic syntax. You will learn how to avoid problems with coroutines, sealed classes, inline functions, and delegated properties. Each section contains specific code examples and recommendations.
1. Incorrect Use of Coroutines: Memory Leaks and Context Loss
Coroutines are a powerful tool for asynchrony, but their incorrect use leads to memory leaks and hard-to-find bugs. The most common mistake is launching a coroutine without binding it to the component's lifecycle.
Mistake: Global scope in Activity
// BAD: coroutine lives longer than the Activityclass MyActivity : AppCompatActivity() { fun loadData() { GlobalScope.launch { val data = fetchFromNetwork() withContext(Dispatchers.Main) { textView.text = data // Leak if Activity is already destroyed } } }}Solution: Use lifecycleScope or viewModelScope
// GOOD: coroutine is cancelled when Activity is destroyedclass MyActivity : AppCompatActivity() { fun loadData() { lifecycleScope.launch { val data = withContext(Dispatchers.IO) { fetchFromNetwork() } textView.text = data } }}Tip: Always use a CoroutineScope tied to the lifecycle. For ViewModel — viewModelScope, for Activity/Fragment — lifecycleScope. Never use GlobalScope in production.
2. Abusing Null Safety: Implicit Assumptions
Kotlin is famous for its null safety, but beginners often create a false sense of security. The mistake is using the !! operator where safe calls could be used.
Mistake: Ignoring nullable types
// BAD: can throw NullPointerExceptionfun processUser(user: User?) { val name = user!!.name println(name)}Solution: Use the Elvis operator or let
// GOOD: safe extractionfun processUser(user: User?) { val name = user?.name ?: "Unknown" println(name) // Or with let: user?.let { u -> println(u.name) }}Important: The !! operator is justified only in tests or when you are 100% sure the value is not null (e.g., after a check in an if). In other cases, use ?.let, ?:, or requireNotNull().
3. Incorrect Use of Sealed Classes and when
Sealed classes are a great way to model restricted hierarchies. However, beginners often forget about when as an expression and do not cover all branches.
Mistake: Missing branches in when
sealed class Result { data class Success(val data: String) : Result() data class Error(val message: String) : Result() object Loading : Result()}
// BAD: the compiler will not warn about missing Loadingfun handleResult(result: Result) { when (result) { is Result.Success -> println(result.data) is Result.Error -> println(result.message) // Loading is forgotten! }}Solution: Use when as an expression
// GOOD: the compiler will force handling all branchesfun handleResult(result: Result): String { return when (result) { is Result.Success -> result.data is Result.Error -> result.message is Result.Loa