kotlin-patterns — ai-agents kotlin-patterns, everything-claude-code, official, ai-agents, ide skills, anthropic, claude-code, developer-tools, Claude Code, Cursor, Windsurf

Verified
v1.0.0
GitHub

About this Skill

Perfect for Android and Backend Agents needing robust Kotlin application development with coroutines, null safety, and DSL builders. Idiomatic Kotlin patterns, best practices, and conventions for building robust, efficient, and maintainable Kotlin applications with coroutines, null safety, and DSL builders.

# Core Topics

affaan-m affaan-m
[116.8k]
[15188]
Updated: 3/30/2026

Agent Capability Analysis

The kotlin-patterns skill by affaan-m is an open-source official AI agent skill for Claude Code and other IDE workflows, helping agents execute tasks with better context, repeatability, and domain-specific guidance. Optimized for ai-agents, anthropic, claude-code.

Ideal Agent Persona

Perfect for Android and Backend Agents needing robust Kotlin application development with coroutines, null safety, and DSL builders.

Core Value

Empowers agents to enforce idiomatic Kotlin conventions, leveraging null safety using the type system and safe-call operators, immutability via `val` and `copy()` on data classes, and structured concurrency with coroutines and `Flow`. It provides best practices for building robust, efficient, and maintainable Kotlin applications with Gradle Kotlin DSL builds.

Capabilities Granted for kotlin-patterns

Writing new Kotlin code with null safety and immutability
Reviewing and refactoring existing Kotlin code for idiomatic conventions
Designing Kotlin modules or libraries with sealed classes and interfaces
Configuring Gradle Kotlin DSL builds for efficient project management
Implementing structured concurrency with async/await and `Flow`

! Prerequisites & Limits

  • Requires Kotlin programming language
  • Needs Gradle build tool for Kotlin DSL configuration
  • Limited to Kotlin-specific development and review tasks
Labs Demo

Browser Sandbox Environment

⚡️ Ready to unleash?

Experience this Agent in a zero-setup browser environment powered by WebContainers. No installation required.

Boot Container Sandbox

kotlin-patterns

Install kotlin-patterns, an AI agent skill for AI agent workflows and automation. Works with Claude Code, Cursor, and Windsurf with one-command setup.

SKILL.md
Readonly

Kotlin Development Patterns

Idiomatic Kotlin patterns and best practices for building robust, efficient, and maintainable applications.

When to Use

  • Writing new Kotlin code
  • Reviewing Kotlin code
  • Refactoring existing Kotlin code
  • Designing Kotlin modules or libraries
  • Configuring Gradle Kotlin DSL builds

How It Works

This skill enforces idiomatic Kotlin conventions across seven key areas: null safety using the type system and safe-call operators, immutability via val and copy() on data classes, sealed classes and interfaces for exhaustive type hierarchies, structured concurrency with coroutines and Flow, extension functions for adding behaviour without inheritance, type-safe DSL builders using @DslMarker and lambda receivers, and Gradle Kotlin DSL for build configuration.

Examples

Null safety with Elvis operator:

kotlin
1fun getUserEmail(userId: String): String { 2 val user = userRepository.findById(userId) 3 return user?.email ?: "unknown@example.com" 4}

Sealed class for exhaustive results:

kotlin
1sealed class Result<out T> { 2 data class Success<T>(val data: T) : Result<T>() 3 data class Failure(val error: AppError) : Result<Nothing>() 4 data object Loading : Result<Nothing>() 5}

Structured concurrency with async/await:

kotlin
1suspend fun fetchUserWithPosts(userId: String): UserProfile = 2 coroutineScope { 3 val user = async { userService.getUser(userId) } 4 val posts = async { postService.getUserPosts(userId) } 5 UserProfile(user = user.await(), posts = posts.await()) 6 }

Core Principles

1. Null Safety

Kotlin's type system distinguishes nullable and non-nullable types. Leverage it fully.

kotlin
1// Good: Use non-nullable types by default 2fun getUser(id: String): User { 3 return userRepository.findById(id) 4 ?: throw UserNotFoundException("User $id not found") 5} 6 7// Good: Safe calls and Elvis operator 8fun getUserEmail(userId: String): String { 9 val user = userRepository.findById(userId) 10 return user?.email ?: "unknown@example.com" 11} 12 13// Bad: Force-unwrapping nullable types 14fun getUserEmail(userId: String): String { 15 val user = userRepository.findById(userId) 16 return user!!.email // Throws NPE if null 17}

2. Immutability by Default

Prefer val over var, immutable collections over mutable ones.

kotlin
1// Good: Immutable data 2data class User( 3 val id: String, 4 val name: String, 5 val email: String, 6) 7 8// Good: Transform with copy() 9fun updateEmail(user: User, newEmail: String): User = 10 user.copy(email = newEmail) 11 12// Good: Immutable collections 13val users: List<User> = listOf(user1, user2) 14val filtered = users.filter { it.email.isNotBlank() } 15 16// Bad: Mutable state 17var currentUser: User? = null // Avoid mutable global state 18val mutableUsers = mutableListOf<User>() // Avoid unless truly needed

3. Expression Bodies and Single-Expression Functions

Use expression bodies for concise, readable functions.

kotlin
1// Good: Expression body 2fun isAdult(age: Int): Boolean = age >= 18 3 4fun formatFullName(first: String, last: String): String = 5 "$first $last".trim() 6 7fun User.displayName(): String = 8 name.ifBlank { email.substringBefore('@') } 9 10// Good: When as expression 11fun statusMessage(code: Int): String = when (code) { 12 200 -> "OK" 13 404 -> "Not Found" 14 500 -> "Internal Server Error" 15 else -> "Unknown status: $code" 16} 17 18// Bad: Unnecessary block body 19fun isAdult(age: Int): Boolean { 20 return age >= 18 21}

4. Data Classes for Value Objects

Use data classes for types that primarily hold data.

kotlin
1// Good: Data class with copy, equals, hashCode, toString 2data class CreateUserRequest( 3 val name: String, 4 val email: String, 5 val role: Role = Role.USER, 6) 7 8// Good: Value class for type safety (zero overhead at runtime) 9@JvmInline 10value class UserId(val value: String) { 11 init { 12 require(value.isNotBlank()) { "UserId cannot be blank" } 13 } 14} 15 16@JvmInline 17value class Email(val value: String) { 18 init { 19 require('@' in value) { "Invalid email: $value" } 20 } 21} 22 23fun getUser(id: UserId): User = userRepository.findById(id)

Sealed Classes and Interfaces

Modeling Restricted Hierarchies

kotlin
1// Good: Sealed class for exhaustive when 2sealed class Result<out T> { 3 data class Success<T>(val data: T) : Result<T>() 4 data class Failure(val error: AppError) : Result<Nothing>() 5 data object Loading : Result<Nothing>() 6} 7 8fun <T> Result<T>.getOrNull(): T? = when (this) { 9 is Result.Success -> data 10 is Result.Failure -> null 11 is Result.Loading -> null 12} 13 14fun <T> Result<T>.getOrThrow(): T = when (this) { 15 is Result.Success -> data 16 is Result.Failure -> throw error.toException() 17 is Result.Loading -> throw IllegalStateException("Still loading") 18}

Sealed Interfaces for API Responses

kotlin
1sealed interface ApiError { 2 val message: String 3 4 data class NotFound(override val message: String) : ApiError 5 data class Unauthorized(override val message: String) : ApiError 6 data class Validation( 7 override val message: String, 8 val field: String, 9 ) : ApiError 10 data class Internal( 11 override val message: String, 12 val cause: Throwable? = null, 13 ) : ApiError 14} 15 16fun ApiError.toStatusCode(): Int = when (this) { 17 is ApiError.NotFound -> 404 18 is ApiError.Unauthorized -> 401 19 is ApiError.Validation -> 422 20 is ApiError.Internal -> 500 21}

Scope Functions

When to Use Each

kotlin
1// let: Transform nullable or scoped result 2val length: Int? = name?.let { it.trim().length } 3 4// apply: Configure an object (returns the object) 5val user = User().apply { 6 name = "Alice" 7 email = "alice@example.com" 8} 9 10// also: Side effects (returns the object) 11val user = createUser(request).also { logger.info("Created user: ${it.id}") } 12 13// run: Execute a block with receiver (returns result) 14val result = connection.run { 15 prepareStatement(sql) 16 executeQuery() 17} 18 19// with: Non-extension form of run 20val csv = with(StringBuilder()) { 21 appendLine("name,email") 22 users.forEach { appendLine("${it.name},${it.email}") } 23 toString() 24}

Anti-Patterns

kotlin
1// Bad: Nesting scope functions 2user?.let { u -> 3 u.address?.let { addr -> 4 addr.city?.let { city -> 5 println(city) // Hard to read 6 } 7 } 8} 9 10// Good: Chain safe calls instead 11val city = user?.address?.city 12city?.let { println(it) }

Extension Functions

Adding Functionality Without Inheritance

kotlin
1// Good: Domain-specific extensions 2fun String.toSlug(): String = 3 lowercase() 4 .replace(Regex("[^a-z0-9\\s-]"), "") 5 .replace(Regex("\\s+"), "-") 6 .trim('-') 7 8fun Instant.toLocalDate(zone: ZoneId = ZoneId.systemDefault()): LocalDate = 9 atZone(zone).toLocalDate() 10 11// Good: Collection extensions 12fun <T> List<T>.second(): T = this[1] 13 14fun <T> List<T>.secondOrNull(): T? = getOrNull(1) 15 16// Good: Scoped extensions (not polluting global namespace) 17class UserService { 18 private fun User.isActive(): Boolean = 19 status == Status.ACTIVE && lastLogin.isAfter(Instant.now().minus(30, ChronoUnit.DAYS)) 20 21 fun getActiveUsers(): List<User> = userRepository.findAll().filter { it.isActive() } 22}

Coroutines

Structured Concurrency

kotlin
1// Good: Structured concurrency with coroutineScope 2suspend fun fetchUserWithPosts(userId: String): UserProfile = 3 coroutineScope { 4 val userDeferred = async { userService.getUser(userId) } 5 val postsDeferred = async { postService.getUserPosts(userId) } 6 7 UserProfile( 8 user = userDeferred.await(), 9 posts = postsDeferred.await(), 10 ) 11 } 12 13// Good: supervisorScope when children can fail independently 14suspend fun fetchDashboard(userId: String): Dashboard = 15 supervisorScope { 16 val user = async { userService.getUser(userId) } 17 val notifications = async { notificationService.getRecent(userId) } 18 val recommendations = async { recommendationService.getFor(userId) } 19 20 Dashboard( 21 user = user.await(), 22 notifications = try { 23 notifications.await() 24 } catch (e: CancellationException) { 25 throw e 26 } catch (e: Exception) { 27 emptyList() 28 }, 29 recommendations = try { 30 recommendations.await() 31 } catch (e: CancellationException) { 32 throw e 33 } catch (e: Exception) { 34 emptyList() 35 }, 36 ) 37 }

Flow for Reactive Streams

kotlin
1// Good: Cold flow with proper error handling 2fun observeUsers(): Flow<List<User>> = flow { 3 while (currentCoroutineContext().isActive) { 4 val users = userRepository.findAll() 5 emit(users) 6 delay(5.seconds) 7 } 8}.catch { e -> 9 logger.error("Error observing users", e) 10 emit(emptyList()) 11} 12 13// Good: Flow operators 14fun searchUsers(query: Flow<String>): Flow<List<User>> = 15 query 16 .debounce(300.milliseconds) 17 .distinctUntilChanged() 18 .filter { it.length >= 2 } 19 .mapLatest { q -> userRepository.search(q) } 20 .catch { emit(emptyList()) }

Cancellation and Cleanup

kotlin
1// Good: Respect cancellation 2suspend fun processItems(items: List<Item>) { 3 items.forEach { item -> 4 ensureActive() // Check cancellation before expensive work 5 processItem(item) 6 } 7} 8 9// Good: Cleanup with try/finally 10suspend fun acquireAndProcess() { 11 val resource = acquireResource() 12 try { 13 resource.process() 14 } finally { 15 withContext(NonCancellable) { 16 resource.release() // Always release, even on cancellation 17 } 18 } 19}

Delegation

Property Delegation

kotlin
1// Lazy initialization 2val expensiveData: List<User> by lazy { 3 userRepository.findAll() 4} 5 6// Observable property 7var name: String by Delegates.observable("initial") { _, old, new -> 8 logger.info("Name changed from '$old' to '$new'") 9} 10 11// Map-backed properties 12class Config(private val map: Map<String, Any?>) { 13 val host: String by map 14 val port: Int by map 15 val debug: Boolean by map 16} 17 18val config = Config(mapOf("host" to "localhost", "port" to 8080, "debug" to true))

Interface Delegation

kotlin
1// Good: Delegate interface implementation 2class LoggingUserRepository( 3 private val delegate: UserRepository, 4 private val logger: Logger, 5) : UserRepository by delegate { 6 // Only override what you need to add logging to 7 override suspend fun findById(id: String): User? { 8 logger.info("Finding user by id: $id") 9 return delegate.findById(id).also { 10 logger.info("Found user: ${it?.name ?: "null"}") 11 } 12 } 13}

DSL Builders

Type-Safe Builders

kotlin
1// Good: DSL with @DslMarker 2@DslMarker 3annotation class HtmlDsl 4 5@HtmlDsl 6class HTML { 7 private val children = mutableListOf<Element>() 8 9 fun head(init: Head.() -> Unit) { 10 children += Head().apply(init) 11 } 12 13 fun body(init: Body.() -> Unit) { 14 children += Body().apply(init) 15 } 16 17 override fun toString(): String = children.joinToString("\n") 18} 19 20fun html(init: HTML.() -> Unit): HTML = HTML().apply(init) 21 22// Usage 23val page = html { 24 head { title("My Page") } 25 body { 26 h1("Welcome") 27 p("Hello, World!") 28 } 29}

Configuration DSL

kotlin
1data class ServerConfig( 2 val host: String = "0.0.0.0", 3 val port: Int = 8080, 4 val ssl: SslConfig? = null, 5 val database: DatabaseConfig? = null, 6) 7 8data class SslConfig(val certPath: String, val keyPath: String) 9data class DatabaseConfig(val url: String, val maxPoolSize: Int = 10) 10 11class ServerConfigBuilder { 12 var host: String = "0.0.0.0" 13 var port: Int = 8080 14 private var ssl: SslConfig? = null 15 private var database: DatabaseConfig? = null 16 17 fun ssl(certPath: String, keyPath: String) { 18 ssl = SslConfig(certPath, keyPath) 19 } 20 21 fun database(url: String, maxPoolSize: Int = 10) { 22 database = DatabaseConfig(url, maxPoolSize) 23 } 24 25 fun build(): ServerConfig = ServerConfig(host, port, ssl, database) 26} 27 28fun serverConfig(init: ServerConfigBuilder.() -> Unit): ServerConfig = 29 ServerConfigBuilder().apply(init).build() 30 31// Usage 32val config = serverConfig { 33 host = "0.0.0.0" 34 port = 443 35 ssl("/certs/cert.pem", "/certs/key.pem") 36 database("jdbc:postgresql://localhost:5432/mydb", maxPoolSize = 20) 37}

Sequences for Lazy Evaluation

kotlin
1// Good: Use sequences for large collections with multiple operations 2val result = users.asSequence() 3 .filter { it.isActive } 4 .map { it.email } 5 .filter { it.endsWith("@company.com") } 6 .take(10) 7 .toList() 8 9// Good: Generate infinite sequences 10val fibonacci: Sequence<Long> = sequence { 11 var a = 0L 12 var b = 1L 13 while (true) { 14 yield(a) 15 val next = a + b 16 a = b 17 b = next 18 } 19} 20 21val first20 = fibonacci.take(20).toList()

Gradle Kotlin DSL

build.gradle.kts Configuration

kotlin
1// Check for latest versions: https://kotlinlang.org/docs/releases.html 2plugins { 3 kotlin("jvm") version "2.3.10" 4 kotlin("plugin.serialization") version "2.3.10" 5 id("io.ktor.plugin") version "3.4.0" 6 id("org.jetbrains.kotlinx.kover") version "0.9.7" 7 id("io.gitlab.arturbosch.detekt") version "1.23.8" 8} 9 10group = "com.example" 11version = "1.0.0" 12 13kotlin { 14 jvmToolchain(21) 15} 16 17dependencies { 18 // Ktor 19 implementation("io.ktor:ktor-server-core:3.4.0") 20 implementation("io.ktor:ktor-server-netty:3.4.0") 21 implementation("io.ktor:ktor-server-content-negotiation:3.4.0") 22 implementation("io.ktor:ktor-serialization-kotlinx-json:3.4.0") 23 24 // Exposed 25 implementation("org.jetbrains.exposed:exposed-core:1.0.0") 26 implementation("org.jetbrains.exposed:exposed-dao:1.0.0") 27 implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0") 28 implementation("org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0") 29 30 // Koin 31 implementation("io.insert-koin:koin-ktor:4.2.0") 32 33 // Coroutines 34 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") 35 36 // Testing 37 testImplementation("io.kotest:kotest-runner-junit5:6.1.4") 38 testImplementation("io.kotest:kotest-assertions-core:6.1.4") 39 testImplementation("io.kotest:kotest-property:6.1.4") 40 testImplementation("io.mockk:mockk:1.14.9") 41 testImplementation("io.ktor:ktor-server-test-host:3.4.0") 42 testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2") 43} 44 45tasks.withType<Test> { 46 useJUnitPlatform() 47} 48 49detekt { 50 config.setFrom(files("config/detekt/detekt.yml")) 51 buildUponDefaultConfig = true 52}

Error Handling Patterns

Result Type for Domain Operations

kotlin
1// Good: Use Kotlin's Result or a custom sealed class 2suspend fun createUser(request: CreateUserRequest): Result<User> = runCatching { 3 require(request.name.isNotBlank()) { "Name cannot be blank" } 4 require('@' in request.email) { "Invalid email format" } 5 6 val user = User( 7 id = UserId(UUID.randomUUID().toString()), 8 name = request.name, 9 email = Email(request.email), 10 ) 11 userRepository.save(user) 12 user 13} 14 15// Good: Chain results 16val displayName = createUser(request) 17 .map { it.name } 18 .getOrElse { "Unknown" }

require, check, error

kotlin
1// Good: Preconditions with clear messages 2fun withdraw(account: Account, amount: Money): Account { 3 require(amount.value > 0) { "Amount must be positive: $amount" } 4 check(account.balance >= amount) { "Insufficient balance: ${account.balance} < $amount" } 5 6 return account.copy(balance = account.balance - amount) 7}

Collection Operations

Idiomatic Collection Processing

kotlin
1// Good: Chained operations 2val activeAdminEmails: List<String> = users 3 .filter { it.role == Role.ADMIN && it.isActive } 4 .sortedBy { it.name } 5 .map { it.email } 6 7// Good: Grouping and aggregation 8val usersByRole: Map<Role, List<User>> = users.groupBy { it.role } 9 10val oldestByRole: Map<Role, User?> = users.groupBy { it.role } 11 .mapValues { (_, users) -> users.minByOrNull { it.createdAt } } 12 13// Good: Associate for map creation 14val usersById: Map<UserId, User> = users.associateBy { it.id } 15 16// Good: Partition for splitting 17val (active, inactive) = users.partition { it.isActive }

Quick Reference: Kotlin Idioms

IdiomDescription
val over varPrefer immutable variables
data classFor value objects with equals/hashCode/copy
sealed class/interfaceFor restricted type hierarchies
value classFor type-safe wrappers with zero overhead
Expression whenExhaustive pattern matching
Safe call ?.Null-safe member access
Elvis ?:Default value for nullables
let/apply/also/run/withScope functions for clean code
Extension functionsAdd behavior without inheritance
copy()Immutable updates on data classes
require/checkPrecondition assertions
Coroutine async/awaitStructured concurrent execution
FlowCold reactive streams
sequenceLazy evaluation
Delegation byReuse implementation without inheritance

Anti-Patterns to Avoid

kotlin
1// Bad: Force-unwrapping nullable types 2val name = user!!.name 3 4// Bad: Platform type leakage from Java 5fun getLength(s: String) = s.length // Safe 6fun getLength(s: String?) = s?.length ?: 0 // Handle nulls from Java 7 8// Bad: Mutable data classes 9data class MutableUser(var name: String, var email: String) 10 11// Bad: Using exceptions for control flow 12try { 13 val user = findUser(id) 14} catch (e: NotFoundException) { 15 // Don't use exceptions for expected cases 16} 17 18// Good: Use nullable return or Result 19val user: User? = findUserOrNull(id) 20 21// Bad: Ignoring coroutine scope 22GlobalScope.launch { /* Avoid GlobalScope */ } 23 24// Good: Use structured concurrency 25coroutineScope { 26 launch { /* Properly scoped */ } 27} 28 29// Bad: Deeply nested scope functions 30user?.let { u -> 31 u.address?.let { a -> 32 a.city?.let { c -> process(c) } 33 } 34} 35 36// Good: Direct null-safe chain 37user?.address?.city?.let { process(it) }

Remember: Kotlin code should be concise but readable. Leverage the type system for safety, prefer immutability, and use coroutines for concurrency. When in doubt, let the compiler help you.

FAQ & Installation Steps

These questions and steps mirror the structured data on this page for better search understanding.

? Frequently Asked Questions

What is kotlin-patterns?

Perfect for Android and Backend Agents needing robust Kotlin application development with coroutines, null safety, and DSL builders. Idiomatic Kotlin patterns, best practices, and conventions for building robust, efficient, and maintainable Kotlin applications with coroutines, null safety, and DSL builders.

How do I install kotlin-patterns?

Run the command: npx killer-skills add affaan-m/everything-claude-code/kotlin-patterns. It works with Cursor, Windsurf, VS Code, Claude Code, and 19+ other IDEs.

What are the use cases for kotlin-patterns?

Key use cases include: Writing new Kotlin code with null safety and immutability, Reviewing and refactoring existing Kotlin code for idiomatic conventions, Designing Kotlin modules or libraries with sealed classes and interfaces, Configuring Gradle Kotlin DSL builds for efficient project management, Implementing structured concurrency with async/await and `Flow`.

Which IDEs are compatible with kotlin-patterns?

This skill is compatible with Cursor, Windsurf, VS Code, Trae, Claude Code, OpenClaw, Aider, Codex, OpenCode, Goose, Cline, Roo Code, Kiro, Augment Code, Continue, GitHub Copilot, Sourcegraph Cody, and Amazon Q Developer. Use the Killer-Skills CLI for universal one-command installation.

Are there any limitations for kotlin-patterns?

Requires Kotlin programming language. Needs Gradle build tool for Kotlin DSL configuration. Limited to Kotlin-specific development and review tasks.

How To Install

  1. 1. Open your terminal

    Open the terminal or command line in your project directory.

  2. 2. Run the install command

    Run: npx killer-skills add affaan-m/everything-claude-code/kotlin-patterns. The CLI will automatically detect your IDE or AI agent and configure the skill.

  3. 3. Start using the skill

    The skill is now active. Your AI agent can use kotlin-patterns immediately in the current project.

Related Skills

Looking for an alternative to kotlin-patterns or another official skill for your workflow? Explore these related open-source skills.

View All

flags

Logo of facebook
facebook

Use when you need to check feature flag states, compare channels, or debug why a feature behaves differently across release channels.

243.6k
0
Developer

extract-errors

Logo of facebook
facebook

Use when adding new error messages to React, or seeing unknown error code warnings.

243.6k
0
Developer

fix

Logo of facebook
facebook

Use when you have lint errors, formatting issues, or before committing code to ensure it passes CI.

243.6k
0
Developer

flow

Logo of facebook
facebook

Use when you need to run Flow type checking, or when seeing Flow type errors in React code.

243.6k
0
Developer