Skill v1.0.1
currentAutomated scan100/100+4 new
version: "1.0.1" name: kotlin description: "Language-specific super-code guidelines for kotlin." risk: safe source: community date_added: "2026-06-16"
Kotlin + Compose: Idiomatic Efficiency Reference
Apply only within the requested scope and current project conventions. Preserve behavior, ownership, error handling, accessibility and security; examples are independent fragments, not equivalent rewrites in every context. Verify language/library versions and run relevant checks. Required tests and user-facing progress/results take precedence over brevity rules. Do not install dependencies or delete files merely to follow these examples.
When to Use
- Use this skill when the task matches this description: Language-specific super-code guidelines for kotlin.
Table of Contents
- Collections & Data Transformation
- Null Safety
- Functions & Lambdas
- Classes & Objects
- Coroutines & Flow
- Compose UI
- Anti-patterns specific to Kotlin
1. Collections & Data Transformation {#collections}
Prefer scope functions and stdlib transforms over imperative loops.
// ❌ Verboseval result = mutableListOf<String>()for (item in items) {if (item.isActive) {result.add(item.name.uppercase())}}// ✅ Idiomaticval result = items.filter { it.isActive }.map { it.name.uppercase() }
// ❌ Manual groupingval map = mutableMapOf<String, MutableList<Item>>()for (item in items) {map.getOrPut(item.category) { mutableListOf() }.add(item)}// ✅val map = items.groupBy { it.category }
// ❌ Manual foldvar total = 0for (order in orders) total += order.amount// ✅val total = orders.sumOf { it.amount }
Use `associate`, `associateBy`, `partition`, `flatMap`, `zip` instead of manual equivalents.
2. Null Safety {#null-safety}
// ❌ Unnecessary null check when Elvis sufficesval name: Stringif (user?.name != null) {name = user.name} else {name = "Guest"}// ✅val name = user?.name ?: "Guest"
// ❌ Double null checkif (response != null && response.body != null) {process(response.body!!)}// ✅response?.body?.let { process(it) }
// ❌ !! without guardval value = nullable!!.doSomething()// ✅ Make the non-null contract explicit at the boundaryval value = requireNotNull(nullable) { "nullable must be set before calling X" }.doSomething()// Or return early:val n = nullable ?: return
3. Functions & Lambdas {#functions}
// ❌ Single-expression function with unnecessary block bodyfun double(x: Int): Int {return x * 2}// ✅fun double(x: Int) = x * 2
// ❌ Lambda capturing unused parameteritems.forEach { item -> doSomething() }// ✅items.forEach { doSomething() }
// ❌ Redundant with/apply nestingval builder = Builder()builder.setName("x")builder.setAge(1)val result = builder.build()// ✅val result = Builder().apply {setName("x")setAge(1)}.build()
Prefer `let`, `run`, `apply`, `also`, `with` over repeated receiver references — but don't nest more than 2 levels deep.
4. Classes & Objects {#classes}
// ❌ Mutable class for immutable dataclass Point {var x: Int = 0var y: Int = 0}// ✅data class Point(val x: Int, val y: Int)
// ❌ Companion object just to hold a constantclass Foo {companion object {val TAG = "Foo"}}// ✅ — top-level if only used in this fileprivate const val TAG = "Foo"class Foo
// ❌ Enum with when that has to be updated in two placesenum class Status { ACTIVE, INACTIVE }fun label(s: Status) = when(s) { Status.ACTIVE -> "Active"; Status.INACTIVE -> "Inactive" }// ✅ — put display logic on the enum itselfenum class Status(val label: String) { ACTIVE("Active"), INACTIVE("Inactive") }
Sealed classes/interfaces over enum when variants carry different data.
5. Coroutines & Flow {#coroutines}
// ❌ Unnecessary async/await pair when result is used immediatelyval result = async { fetchData() }.await()// ✅val result = fetchData() // just suspend fun, no async needed
// ❌ Collecting in a loopwhile (true) {val value = channel.receive()process(value)}// ✅channel.consumeEach { process(it) }// or for Flow:flow.collect { process(it) }
// ❌ StateFlow + manual emit boilerplateprivate val _state = MutableStateFlow(initial)val state: StateFlow<State> = _state// ... in many places: _state.value = newValue// ✅ — use update{} for atomic mutation_state.update { it.copy(field = newValue) }
Don't launch coroutines in constructors or init blocks. Don't use GlobalScope.
6. Compose UI {#compose}
// ❌ Unnecessary remember for derived state that's cheap to computeval displayName = remember { user.firstName + " " + user.lastName }// ✅ — only remember if computation is expensive or involves object creationval displayName = "${user.firstName} ${user.lastName}"
// ❌ Passing entire state object when composable only needs one field@Composablefun UserBadge(user: User) { Text(user.name) }// ✅ — pass only what's needed (stability + minimal recomposition)@Composablefun UserBadge(name: String) { Text(name) }
// ❌ Inline click handler lambda (creates new instance each recomposition)Button(onClick = { viewModel.onSave() }) { ... }// ✅val onSave = remember(viewModel) { { viewModel.onSave() } }Button(onClick = onSave) { ... }// Or pass it down as a parameter already
// ❌ Nested Column/Row just to group childrenColumn {Column {Text("a")Text("b")}}// ✅Column {Text("a")Text("b")}
Use `LazyColumn`/`LazyRow` for lists of unknown or large size. Never put a `LazyColumn` inside a `Column` with unbounded height.
7. Anti-patterns specific to Kotlin {#antipatterns}
| Anti-pattern | Preferred | |
|---|---|---|
if (x == true) | if (x) | |
if (x == null) return else x!! | val x = x ?: return | |
listOf().toMutableList() for a known-size list | mutableListOf() | |
when with a single branch and else | if/else | |
.toString() on a string | remove it | |
Explicit Unit return type on functions | omit (inferred) | |
object : Runnable { override fun run() { ... } } | Runnable { ... } (SAM) | |
@JvmStatic in pure Kotlin code | only needed for Java interop | |
| Wrapping every function in try/catch to log | handle at the boundary, not inside |
Limitations
- These are language-specific guidelines and do not cover overall architectural decisions.
- Over-compression might reduce readability; apply judgement.