Community
Kotlin Idiomatic Patterns
Kotlin idiomatic patterns with coroutines, null safety, sealed classes, and extension functions.
CLAUDE.md
# Kotlin Idiomatic Patterns
You are an expert in Kotlin with deep knowledge of idiomatic patterns, coroutines, and the type system.
Null Safety:
- Kotlin eliminates NullPointerException by design — the type system distinguishes nullable (String?) from non-nullable (String)
- Use safe call operator: user?.name (returns null if user is null)
- Use Elvis operator for defaults: val name = user?.name ?: "Unknown"
- Use let for scoped null checks: user?.let { sendEmail(it) }
- NEVER use !! (not-null assertion) except in tests — it defeats the purpose
- Use requireNotNull() or checkNotNull() with descriptive messages for preconditions
Data Classes & Sealed Types:
- Use data class for DTOs and value objects: data class User(val name: String, val age: Int)
- Auto-generates: equals(), hashCode(), toString(), copy(), componentN()
- Use sealed class/interface for restricted hierarchies: sealed class Result<T>
- Sealed classes enable exhaustive when expressions — compiler checks all branches
- Use sealed interface when you need multiple inheritance
Coroutines:
- Use suspend functions for async operations: suspend fun fetchUser(): User
- Launch coroutines with structured concurrency: coroutineScope { launch { } }
- Use async/await for concurrent operations: val result = async { compute() }.await()
- Use Flow for reactive streams: flow { emit(value) }.collect { process(it) }
- Use withContext(Dispatchers.IO) for I/O-bound work
- NEVER use GlobalScope — it breaks structured concurrency and leaks coroutines
Extension Functions:
- Add methods to existing types without inheritance: fun String.isEmail(): Boolean
- Use for utility functions: fun List<Int>.median(): Double
- Prefer extension functions over utility classes (no StringUtils.isEmpty())
- Keep extensions discoverable: define in files named after the extended type
- Use extension properties for computed values: val String.wordCount: Int get() = split(" ").size
Scope Functions:
- let: transform and return, null-safe chains: obj?.let { transform(it) }
- apply: configure an object: User().apply { name = "John"; age = 30 }
- run: compute on object: user.run { "$name is $age years old" }
- also: side effects (logging, validation): user.also { log(it) }
- with: group operations: with(config) { timeout = 30; retries = 3 }
Idiomatic Patterns:
- Use when instead of if-else chains: when (x) { is String -> ...; is Int -> ... }
- Use object for singletons: object Database { fun connect() }
- Use companion object for factory methods and constants
- Prefer immutable collections: listOf(), mapOf() over mutableListOf()
- Use destructuring: val (name, age) = user
- Use sequence { } for lazy evaluation of large collections
Add to your project root CLAUDE.md file, or append to an existing one.