# Kotlin Patterns

> When to activate: Kotlin idioms, data classes, sealed classes, when expression, companion objects, operator overloading, extension functions, DSL, scope functions

- Skill: `mattakushi432/kotlin-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/kotlin-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/kotlin-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/kotlin-patterns

---

# Kotlin Patterns & Idioms

## Data Classes & Destructuring

```kotlin
data class User(
    val id: Long,
    val name: String,
    val email: String,
    val role: Role = Role.USER
)

// Destructuring
val (id, name, email) = user

// Copy with changes (immutable update)
val updated = user.copy(name = "Jane", role = Role.ADMIN)
```

## Sealed Classes & When

```kotlin
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String, val cause: Throwable? = null) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

fun <T> Result<T>.getOrElse(default: T): T = when (this) {
    is Result.Success -> data
    is Result.Error -> default
    Result.Loading -> default
}

// Exhaustive when (no else needed for sealed)
fun handleResult(result: Result<User>) = when (result) {
    is Result.Success -> println("Got user: ${result.data.name}")
    is Result.Error -> println("Error: ${result.message}")
    Result.Loading -> println("Loading...")
}
```

## Extension Functions

```kotlin
fun String.isValidEmail(): Boolean =
    matches(Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"))

fun <T> List<T>.secondOrNull(): T? = if (size >= 2) get(1) else null

fun BigDecimal.format(currency: Currency = Currency.getInstance("USD")): String =
    NumberFormat.getCurrencyInstance().apply { this.currency = currency }.format(this)

// Extension on existing types
fun LocalDate.isWeekend(): Boolean = dayOfWeek in setOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY)
```

## Scope Functions

```kotlin
// let — transform nullable or create a scope
user?.let { println("User: ${it.name}") }
val length = name?.let { it.trim().length } ?: 0

// apply — configure object, returns receiver
val user = User().apply {
    name = "Alice"
    email = "alice@example.com"
}

// run — execute block, return result
val summary = user.run {
    "${name} (${email})"
}

// also — side effects, returns receiver
repository.save(user).also { log.info("Saved user ${it.id}") }

// with — call multiple methods, no receiver
val result = with(stringBuilder) {
    append("Hello")
    append(", World")
    toString()
}
```

## Companion Objects & Factory Methods

```kotlin
class ApiClient private constructor(
    private val baseUrl: String,
    private val apiKey: String
) {
    companion object {
        fun create(baseUrl: String, apiKey: String): ApiClient {
            require(baseUrl.isNotBlank()) { "baseUrl cannot be blank" }
            require(apiKey.length >= 32) { "apiKey must be at least 32 chars" }
            return ApiClient(baseUrl, apiKey)
        }

        val DEFAULT = create("https://api.example.com", System.getenv("API_KEY") ?: "")
    }
}
```

## Operator Overloading

```kotlin
data class Money(val amount: BigDecimal, val currency: String) {
    operator fun plus(other: Money): Money {
        require(currency == other.currency) { "Currency mismatch" }
        return copy(amount = amount + other.amount)
    }

    operator fun times(factor: Int): Money = copy(amount = amount * factor.toBigDecimal())
    operator fun compareTo(other: Money): Int = amount.compareTo(other.amount)
}

val total = Money(10.bd, "USD") + Money(5.bd, "USD") // Money(15, USD)
```

## Type-Safe DSL Builder

```kotlin
@DslMarker annotation class EmailDsl

@EmailDsl
class EmailBuilder {
    var to: String = ""
    var subject: String = ""
    private val body = StringBuilder()

    fun body(block: StringBuilder.() -> Unit) { body.block() }
    fun build() = Email(to, subject, body.toString())
}

fun email(block: EmailBuilder.() -> Unit): Email = EmailBuilder().apply(block).build()

// Usage
val msg = email {
    to = "user@example.com"
    subject = "Welcome"
    body { append("Hello, World!") }
}
```

## Delegation

```kotlin
// Property delegation
class UserPreferences(private val prefs: SharedPreferences) {
    var darkMode: Boolean by prefs.boolean("dark_mode", default = false)
    var language: String by prefs.string("language", default = "en")
}

// Interface delegation
class LoggingRepository(
    private val delegate: UserRepository
) : UserRepository by delegate {
    override fun save(user: User): User {
        log.info("Saving user ${user.id}")
        return delegate.save(user).also { log.info("Saved user ${it.id}") }
    }
}
```

## Inline Functions & Reified Types

```kotlin
inline fun <reified T> String.fromJson(): T = objectMapper.readValue(this, T::class.java)

inline fun <reified T : ViewModel> Fragment.viewModel(): T =
    ViewModelProvider(this)[T::class.java]

// measureTime — stdlib
val (result, duration) = measureTimedValue {
    expensiveOperation()
}
```

## Key Rules
- Prefer `val` over `var`; immutability is the default
- Use `data class` for value objects; `sealed class` for algebraic types
- Scope functions: `let` for null checks, `apply` for builder patterns, `also` for side effects
- Avoid `!!` (non-null assertion) — use `?: throw`, `requireNotNull`, or safe navigation
- `companion object` is the Kotlin equivalent of Java static — don't abuse it for unrelated utilities

