Notes from a Q&A session (October 2026)
Kotlin is a statically typed, general-purpose programming language created by JetBrains, the company behind IntelliJ IDEA. Development started in 2011 and version 1.0 came out in 2016. It was designed as a more concise, safer and more modern alternative to Java that still works with existing Java code in both directions. Its main features:
String?), so null pointer errors are caught when compiling.build.gradle.kts), which is now Gradle's default.A very large monorepo, millions of lines and mostly written in Kotlin itself, built with Gradle.
| Directory | What it is |
|---|---|
compiler/ |
The compiler itself, the core of the repo |
compiler/fir/ |
K2 frontend ("FIR"): parsing, name resolution, type checking. Default since Kotlin 2.0 |
compiler/ir/ |
Intermediate representation and the backends that generate code from it |
compiler/cli/ |
Command-line compiler entry points |
libraries/stdlib/ |
The Kotlin standard library |
libraries/tools/kotlin-gradle-plugin/ |
The Gradle plugin that most users interact with |
kotlin-native/ |
Native backend and runtime (LLVM based) |
js/, wasm/ |
JavaScript and WebAssembly targets |
plugins/ |
Compiler plugins: serialization, all-open, no-arg, Compose compiler, and others |
analysis/ |
Analysis API used by the IDE and by tools like KSP |
compiler/testData/ |
Huge set of test-input .kt files |
K2JVMCompiler in compiler/cli/ runs when you call kotlinc for the JVM. K2JSCompiler and K2Native do the same job for the JS and native targets.KotlinCompile task is how most real projects call the compiler.ReadMe.md and docs/ at the repo root explain building and organization.| IDE | Best for | Notes |
|---|---|---|
| IntelliJ IDEA | General Kotlin, server side, multiplatform, working on the compiler | The reference IDE. Free Community Edition is enough. Default choice. |
| Android Studio | Android apps | Built on IntelliJ, with emulator, Compose previews, profilers. Free. |
| Fleet | Kotlin Multiplatform | Lighter, newer IDE from JetBrains; less mature. |
| VS Code | Light editing | Official Kotlin LSP extension (or community fwcd/kotlin-language-server). Clearly weaker than IntelliJ. |
| Eclipse | Not recommended | Old plugin, barely maintained. |
build.gradle.kts): dominant; standard for Android and multiplatform.kotlin-maven-plugin: common in older Spring/Java shops.kotlinc: standalone compiler (install via SDKMAN, Homebrew, or Snap)..main.kts) for quick scripts.For beginners: install IntelliJ IDEA Community Edition and create a new Kotlin project with Gradle.
A linter reads source code without running it and flags problems by checking it against a set of rules. The name comes from lint, a 1978 Unix tool that checked C code.
It catches:
catch blocks.fun calc(x: Int): Int {
val unused = 5 // linter: unused variable
if (x == x) { // linter: condition is always true
return x * 86400 // linter: magic number, use a named constant
}
return 0
}
This compiles fine. The compiler checks whether code is valid; a linter checks whether it's good.
| Tool | Job |
|---|---|
| Compiler | Is the code valid? Can it be turned into a runnable program? |
| Formatter | Rewrites layout automatically (spaces, line breaks) |
| Linter | Warns about style, likely bugs and bad patterns; can fix some issues automatically |
Linters run in the IDE (IntelliJ "inspections"), on the command line/build (./gradlew detekt), in CI, and in git pre-commit hooks.
Kotlin has no single built-in UI framework. The main one today is Jetpack Compose, plus Compose Multiplatform.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Clicked $count times")
}
}
When count changes, Compose redraws only the affected parts (recomposition). Compose depends on a Kotlin compiler plugin (plugins/compose in the Kotlin repo).
| Target | Status |
|---|---|
| Android | Stable (this is Jetpack Compose itself) |
| Desktop (Windows, macOS, Linux) | Stable |
| iOS | Stable since 2025 |
| Web (via WebAssembly) | Beta |
| Framework | Use |
|---|---|
| Android Views + XML | Older imperative approach; being replaced by Compose |
| Swing / JavaFX | Old Java desktop toolkits; IntelliJ's own UI is Swing |
| SwiftUI (native) | Share logic in Kotlin, write the iOS UI natively |
| Kobweb, KVision | Kotlin frameworks for websites |
| TornadoFX | Kotlin wrapper around JavaFX; abandoned |
Kotlin backends almost always run on the JVM, exactly like Java.
.kt source → kotlinc (via Gradle/Maven) → .class files (bytecode) → packaged into a .jar
java -jar myservice.jar.Developer pushes code
↓
CI (GitHub Actions, GitLab CI, Jenkins, …)
• ./gradlew build → compile, test, lint
• produce JAR
• docker build → wrap JAR in an image
• push image to a registry
↓
Deployment
• Kubernetes, AWS ECS, Google Cloud Run, Azure, …
• container starts → JVM starts → app listens on a port
# Stage 1: build
FROM gradle:8-jdk21 AS build
COPY . /app
WORKDIR /app
RUN gradle bootJar --no-daemon
# Stage 2: run
FROM eclipse-temurin:21-jre
COPY --from=build /app/build/libs/app.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
Jib or Spring Boot's bootBuildImage can build images without a Dockerfile.
| Option | How common | Notes |
|---|---|---|
| Container on Kubernetes / cloud container services | Most common | Standard for microservices |
| JAR on a VM or server | Older/simpler setups | Often a systemd service |
| Serverless (Lambda, Cloud Functions) | Niche | JVM cold starts are slow |
| GraalVM native image | Growing | Fast startup, low memory, slower builds. Spring Boot 3, Quarkus, Micronaut |
| Kotlin/Native on servers | Rare | Small server library ecosystem |
Spring Boot and Quarkus are DI (dependency injection) containers, also called IoC containers. Ktor ships without one; you add one if you want it.
Your code declares what it needs; the container builds objects and connects them at startup:
@Service
class OrderService(
private val repo: OrderRepository, // injected
private val payment: PaymentClient // injected
)
@Service, @Component, …).| DI approach | When wiring happens | |
|---|---|---|
| Spring Boot | Own IoC container, most feature-rich | Runtime (reflection, classpath scanning); slower startup |
| Quarkus | CDI (Jakarta standard) via ArC | Build time; fast startup, good for native images |
| Micronaut | Own DI | Build time via annotation processing (KSP) |
| Ktor | Usually Koin or manual wiring; lightweight DI plugin in Ktor 3.2+ | Runtime, explicit |
// Ktor + Koin
val appModule = module {
single { OrderRepository() }
single { OrderService(get()) }
}
| Area | Spring Boot | Quarkus | Ktor |
|---|---|---|---|
| HTTP server | Tomcat, Netty, Jetty | Vert.x / Netty | Netty, CIO, Jetty |
| REST | Spring Web MVC / WebFlux | RESTEasy Reactive (JAX-RS) | Routing DSL |
| JSON | Jackson (+ Kotlin module) | Jackson / JSON-B | kotlinx.serialization (or Jackson, Gson) |
| Reactive / async | Project Reactor, coroutines | Mutiny, coroutines | Coroutines natively |
| Streaming | SSE, WebSockets, Spring Cloud Stream | SSE, WebSockets, SmallRye Kafka | SSE, WebSockets |
| Database | Spring Data (JPA/JDBC/R2DBC) | Hibernate ORM / Panache | Pick your own, e.g. Exposed |
| Security | Spring Security | Quarkus Security / OIDC | Auth plugins |
| Config, metrics, health | Actuator | SmallRye Health / Micrometer | Plugins |
"Container" has three meanings here: the DI container, the embedded HTTP server (historically a servlet container), and the Docker container.
kotlin-stdlib, ~1.5–2 MB)map, filter, groupBy, sortedBy, …)Regex, buildersInt, String, Pair, Result, rangeslet, apply, also, run, withkotlin.time.DurationFile.readText())Also: kotlin-reflect (full reflection, optional) and kotlin-test.
kotlinx.* libraries (about 10–15)| Library | Purpose |
|---|---|
| kotlinx.coroutines | launch, async, Flow, channels |
| kotlinx.serialization | JSON, CBOR, ProtoBuf via a compiler plugin |
| kotlinx-datetime | Multiplatform dates and times |
| kotlinx-io | Multiplatform I/O |
| kotlinx.collections.immutable | Persistent/immutable collections |
| kotlinx.html | HTML DSL |
| kotlinx.atomicfu | Atomic operations across platforms |
| kotlinx-cli | Command-line parsing |
| kotlinx-benchmark | Microbenchmarks |
| Kotlin DataFrame | Data analysis (like pandas) |
| kotlinx-rpc | RPC between Kotlin services |
| Language | Standard library |
|---|---|
| Python, Go | Large: HTTP, JSON, files built in |
| Java | Large on the JVM |
| Kotlin | Small on purpose + kotlinx + the Java ecosystem |
Two forms:
// Java
List<String> names = users.stream()
.filter(u -> u.getAge() > 18)
.map(User::getName)
.sorted()
.collect(Collectors.toList());
// Kotlin
val names = users
.filter { it.age > 18 }
.map { it.name }
.sorted()
No .stream(), no .collect(...); you get a normal List.
| Collection operations | Sequence |
|
|---|---|---|
| Evaluation | Eager, intermediate lists | Lazy, element by element |
| Java equivalent | none | Stream |
| Best for | Small/medium collections (most cases) | Large data, long chains, infinite data, early stop |
users.asSequence()
.filter { it.age > 18 }
.map { it.name }
.first()
generateSequence(1) { it * 2 }.take(10).toList() // [1, 2, 4, ..., 512]
map, flatMap, mapNotNull, mapIndexedfilter, filterNot, filterIsInstance<T>(), distinct, take, drop, takeWhilesum, sumOf { }, count, maxBy, minOf, average, reduce, foldgroupBy, partition, associateBy, chunked(n), windowed(n), zipfirst, firstOrNull, find, any, all, nonesorted, sortedBy { }, sortedWith(compareBy({ it.a }, { it.b }))val byCity: Map<String, List<User>> = users.groupBy { it.city }
val (adults, minors) = users.partition { it.age >= 18 }
val total = orders.sumOf { it.price }
stream.asSequence() / sequence.asStream().parallelStream() equivalent: use Java's or coroutines.Flow (kotlinx.coroutines), same operators, works with suspending code.Collections are generic like Java's (List<String>, Map<String, Int>), with improvements.
val names = listOf("Ann", "Bob") // List<String> inferred
val ages = mapOf("Ann" to 30) // Map<String, Int>
val empty = mutableListOf<User>() // type needed for empty lists
1. Read-only vs. mutable types
val a: List<String> = listOf("x") // no add()/remove()
val b: MutableList<String> = mutableListOf("x")
b.add("y") // OK
a.add("y") // compile error
(List is read-only, not truly immutable; for that, use kotlinx.collections.immutable.)
2. Nullability in type arguments
List<String> // non-null list, non-null elements
List<String?> // elements can be null
List<String>? // the list itself can be null
3. Declaration-site variance (out / in)
interface List<out E>
val ints: List<Int> = listOf(1, 2)
val nums: List<Number> = ints // OK: covariant
val mInts: MutableList<Int> = mutableListOf(1)
val mNums: MutableList<Number> = mInts // error: MutableList is invariant
out T ≈ ? extends T (produces T)in T ≈ ? super T (consumes T)* ≈ ? (e.g. List<*>)4. reified type parameters (work around type erasure in inline functions)
inline fun <reified T> List<Any>.only(): List<T> = filterIsInstance<T>()
listOf(1, "a", 2, "b").only<String>() // ["a", "b"]
Your own generics
class Box<T>(val value: T)
fun <T> firstOrDefault(list: List<T>, default: T): T = list.firstOrNull() ?: default
fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a > b) a else b
Kotlin has real function types like (Int) -> String instead of functional interfaces.
// Java
Function<Integer, Integer> square = x -> x * x;
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
Runnable r = () -> System.out.println("hi");
// Kotlin
val square: (Int) -> Int = { x -> x * x }
val add: (Int, Int) -> Int = { a, b -> a + b }
val r: () -> Unit = { println("hi") }
square(4) // 16
| Java | Kotlin |
|---|---|
Runnable |
() -> Unit |
Supplier<T> |
() -> T |
Consumer<T> |
(T) -> Unit |
Function<T, R> |
(T) -> R |
BiFunction<A, B, R> |
(A, B) -> R |
Predicate<T> |
(T) -> Boolean |
list.filter { it > 0 } // 1. implicit "it"
list.forEach { println(it) } // 2. trailing lambda outside ()
val classify = { n: Int -> // 3. last expression is returned
if (n > 100) "big" else "small"
}
var count = 0; list.forEach { count++ } // 4. captured vars can be modified
map.forEach { (key, value) -> println("$key=$value") } // 5. destructuring
Trailing lambdas enable DSLs (Compose, Ktor routing, Gradle scripts).
list.map(String::uppercase)
list.forEach(::println)
list.map(user::format)
executor.submit { doWork() }
button.setOnClickListener { handleClick() }
For your own interfaces, use fun interface.
return inside lambdasfun findFirst(list: List<Int>) {
list.forEach {
if (it > 5) return // returns from findFirst() itself!
}
}
list.forEach {
if (it > 5) return@forEach // labeled return: like "continue"
}
Kotlin has the practical core of FP but deliberately leaves out Scala's advanced type-level features.
// Higher-order functions
fun compose(f: (Int) -> Int, g: (Int) -> Int): (Int) -> Int = { x -> f(g(x)) }
// Immutability by default
val x = 5; val list = listOf(1, 2)
// Expressions
val label = if (n > 0) "pos" else "neg"
val parsed = try { s.toInt() } catch (e: Exception) { 0 }
// Data classes with copy
data class User(val name: String, val age: Int)
val older = user.copy(age = user.age + 1)
// Algebraic data types: sealed + exhaustive when
sealed interface Shape
data class Circle(val r: Double) : Shape
data class Rect(val w: Double, val h: Double) : Shape
fun area(s: Shape): Double = when (s) {
is Circle -> Math.PI * s.r * s.r
is Rect -> s.w * s.h
}
Also: tailrec, Sequence / by lazy, null safety instead of Option, Result<T>, extension functions.
| Scala feature | Kotlin |
|---|---|
| Full pattern matching (nested destructuring, guards, extractors) | when is more limited |
Higher-kinded types (F[_]) |
Not supported |
Type classes via implicits / given |
Not built in; context parameters partly cover this |
for-comprehensions |
No equivalent |
| Persistent immutable collections in stdlib | Only via kotlinx.collections.immutable |
| Union / intersection / path-dependent types | Not supported ("rich errors" bring union types for errors only) |
| Cats / ZIO | Arrow (smaller equivalent) |
fun parse(s: String): Either<String, Int> =
s.toIntOrNull()?.right() ?: "not a number".left()
Provides Either, Raise, Option, optics, resource safety.
Summary: Kotlin is pragmatic; for pure FP with monads, type classes and higher-kinded types, Scala (or Haskell) goes further. Arrow narrows the gap.
Same mechanism and classes as Java. The big difference: no checked exceptions.
fun read(p: Path): String = Files.readString(p) // no throws clause needed
@Throws(IOException::class) // for Java callers
fun readForJava(p: Path): String = ...
try and throw are expressionsval n: Int = try { input.toInt() } catch (e: NumberFormatException) { 0 }
val name = user.name ?: throw IllegalArgumentException("name missing")
throw has type Nothing.
use instead of try-with-resourcesFiles.newBufferedReader(path).use { reader -> reader.readLine() }
require(age >= 0) { "age must be >= 0" } // IllegalArgumentException
check(isConnected) { "not connected" } // IllegalStateException
val user = requireNotNull(findUser(id))
error("unreachable") // IllegalStateException
TODO("implement later") // NotImplementedError
val n = "abc".toIntOrNull() // null instead of exception
val result: Result<Int> = runCatching { input.toInt() }
result.map { it * 2 }.onFailure { log.warn("bad input", it) }.getOrDefault(0)
sealed interface LoginResult {
data class Success(val user: User) : LoginResult
data object WrongPassword : LoginResult
data object Locked : LoginResult
}
Note: runCatching also catches CancellationException, which breaks coroutine cancellation. Arrow's Either/Raise go further. "Rich errors" (fun parse(): Int | ParseError) are experimental.
when (e) { is ... }.SupervisorJob/supervisorScope).launch → CoroutineExceptionHandler; async → exception thrown at await().CancellationException.| Java | Kotlin | |
|---|---|---|
| Checked exceptions | Yes | No (@Throws for Java callers) |
try |
Statement | Expression |
throw |
Statement | Expression of type Nothing |
| Resource cleanup | try-with-resources | .use { } |
| Preconditions | Objects.requireNonNull, manual |
require, check, error, requireNotNull |
| Multi-catch | Yes | No |
| Preferred style | Throw/catch | Exceptions for bugs; null, Result, sealed types for expected failures |
Kotlin can use all of Java's tools, plus coroutines (kotlinx.coroutines).
thread { doWork() }
Executors.newFixedThreadPool(4).submit { doWork() }
@Volatile var running = true
@Synchronized fun inc() { counter++ }
synchronized(lock) { counter++ }
val counter = AtomicInteger()
A coroutine is a lightweight task that can pause without blocking a thread.
suspend fun loadUser(id: Long): User {
val profile = api.fetchProfile(id) // suspends, thread is free
val orders = api.fetchOrders(id)
return User(profile, orders)
}
The compiler turns suspend functions into state machines.
// Parallel work
suspend fun dashboard() = coroutineScope {
val user = async { api.fetchUser() }
val news = async { api.fetchNews() }
Dashboard(user.await(), news.await())
}
// Java equivalent
CompletableFuture<User> u = supplyAsync(api::fetchUser);
CompletableFuture<News> n = supplyAsync(api::fetchNews);
u.thenCombine(n, Dashboard::new).join();
// Dispatchers
withContext(Dispatchers.IO) { readFile() }
withContext(Dispatchers.Default) { heavyMath() }
withContext(Dispatchers.Main) { updateUi() }
Every coroutine belongs to a scope: parents wait for children; one failure cancels siblings and propagates; cancelling a scope stops everything in it.
coroutineScope {
launch { task1() }
launch { task2() } // if task2 throws, task1 is cancelled
} // reached only when both finished
Java 21 has StructuredTaskScope (still preview).
val job = launch {
while (isActive) { poll(); delay(1000) }
}
job.cancel()
| Tool | Purpose | Java equivalent |
|---|---|---|
Channel |
Pass values between coroutines | BlockingQueue |
Flow |
Async stream of values | Reactive Streams / RxJava |
StateFlow / SharedFlow |
Observable state, events | BehaviorSubject / listeners |
Mutex |
Suspending lock | ReentrantLock |
Semaphore |
Limit concurrency | Semaphore |
fun prices(): Flow<Price> = flow {
while (true) { emit(api.price()); delay(1000) }
}
prices().filter { it.value > 100 }.collect { println(it) }
| Coroutines | Virtual threads | |
|---|---|---|
| Lightweight | Yes | Yes |
| Blocking-style async code | Yes (suspend) |
Yes (plain blocking calls) |
| Structured concurrency | Mature, default | Preview |
| Cancellation | Built in | Interruption |
| Streams, channels | Yes | No |
| Multiplatform | Yes | JVM only |
| Function marking | suspend spreads |
None |
They can be combined (dispatcher on virtual threads).
Same pitfalls as Java. Use Mutex, atomics, thread-safe collections, single-thread confinement (Dispatchers.Default.limitedParallelism(1)), or immutable state via StateFlow.
For new projects, Kotlin in most cases. Java is better in situations driven by team and existing code.
| Use case | Why |
|---|---|
| Android | Google is Kotlin-first; Compose is Kotlin-only |
| Code shared across Android, iOS, desktop, web | Kotlin Multiplatform; no Java equivalent |
| New backend services | Less boilerplate, null safety, coroutines, data/sealed classes |
| Domain-heavy business logic | Sealed classes, data classes, copy() |
| Heavily async code | Coroutines, structured concurrency, Flow |
| DSLs and configuration | Gradle, HTML builders, Ktor routing |
| Small teams wanting speed | Less code to write and review |
| Use case | Why |
|---|---|
| Large existing Java codebases | Two languages have real costs; migration rarely pays off |
| Java-only teams, large organizations | Hiring, talent pool, one set of conventions |
| Libraries for the wider JVM ecosystem | Cleanest API for Java callers |
| Long-lived systems prioritizing stability | Java's backward compatibility, open governance |
| Max performance / JVM internals | Tooling and docs assume Java; Kotlin can generate hidden extra code |
| Build speed matters a lot | kotlinc is slower than javac |
Modern Java (21+): records, sealed interfaces + pattern-matching switch, var, text blocks, virtual threads.
Java still lacks: null safety in the type system, extension functions, default/named parameters, properties, read-only vs. mutable collections, multiplatform, trailing-lambda DSLs.
| Area | Java | Kotlin |
|---|---|---|
| IntelliJ IDEA | Excellent | Excellent |
| Eclipse | Excellent | Poor |
| VS Code | Good | Okay (newer LSP) |
| NetBeans | Good | Effectively none |
| Android Studio | Supported | Best supported |
| Build tools | Maven, Gradle | Gradle standard; Maven works |
| Compile speed | Fast | Slower (K2 helps) |
| Incremental builds | Very good | Good; inline/ABI changes cause wider rebuilds |
| Static analysis | Most mature: SpotBugs, PMD, Checkstyle, Error Prone, Sonar, NullAway | detekt, ktlint, Android Lint, Sonar |
| SAST | Everywhere | Most major ones |
| Coverage | JaCoCo | JaCoCo, or Kover for better accuracy |
| Debugging/profiling | Excellent | Same JVM tools + coroutine debugger |
| Bytecode inspection | Readable | More synthetic code; IntelliJ decompiles to Java |
| AI coding tools | Very good | Good |
| Area | Java | Kotlin |
|---|---|---|
| Library count | Huge | Same on the JVM |
| Kotlin-first libraries | — | Ktor, Exposed, Koin, Arrow, kotlinx.serialization, SQLDelight, Compose |
| Frameworks | Spring, Quarkus, Micronaut, Jakarta EE, Helidon | Same + Ktor; Jakarta EE weaker |
| Annotation processing | APT | KSP, or kapt (deprecated) |
| Bytecode-changing libraries | Lombok, MapStruct, Hibernate | No Lombok (not needed); JPA needs no-arg/all-open plugins |
| Serialization | Jackson, Gson | Jackson + Kotlin module, kotlinx.serialization; Gson can break null safety |
| Multiplatform | No | Yes, but no Java libraries there |
| Docs and Q&A | Largest | Smaller; Java answers translate |
| Talent pool | Much larger | Smaller; strong in Android |
| Governance | OpenJDK, multiple vendors | JetBrains + Kotlin Foundation |
| Area | Java | Kotlin |
|---|---|---|
| Frameworks | JUnit 5, TestNG | JUnit 5, Kotest, kotlin.test |
| Mocking | Mockito | MockK (finals, objects, extensions, coroutines) or mockito-kotlin |
| Final classes | Not final by default | Final by default; needs MockK or all-open |
| Readability | Verbose | Backtick names, data class fixtures, named/default params |
| Assertions | AssertJ, Hamcrest | AssertJ, Kotest assertions, Strikt, AssertK |
| Test data | Builders / Lombok | copy(), defaults, apply { } |
| Async tests | Awaitility | runTest (virtual time), Turbine for Flow |
| Spring Boot tests | Excellent | Excellent + Kotlin DSLs |
| Testcontainers | Excellent | Excellent |
| Mutation testing | PIT | PIT (some false positives) |
| Null-related bugs | Need tests | Ruled out by compiler |
class UserServiceTest : StringSpec({
val repo = mockk<UserRepository>()
val service = UserService(repo)
"returns user when found" {
every { repo.find(1) } returns User(1, "Ann")
service.get(1).name shouldBe "Ann"
}
})
| Winner | Margin | |
|---|---|---|
| IDE support (JetBrains IDEs) | Tie | — |
| IDE support (other editors) | Java | Large |
| Build speed | Java | Moderate |
| Static analysis / security tooling | Java | Moderate |
| Library availability (JVM) | Tie | Small |
| Framework friction | Java | Small–moderate |
| Multiplatform | Kotlin | Java doesn't compete |
| Talent pool / docs | Java | Large |
| Writing/reading tests | Kotlin | Moderate |
| Async testing | Kotlin | Large |
Practically the same. Both compile to JVM bytecode and run on the same JVM, JIT and GC.
Java → javac → bytecode ┐
├→ same JVM → same machine code
Kotlin → kotlinc → bytecode ┘
| Pattern | Cost | Avoid by |
|---|---|---|
Eager chains filter{}.map{}.first() |
Intermediate lists | asSequence() or loops on hot paths |
Int?, List<Int> |
Boxing (same as Java) | Int, IntArray |
| Lambdas to non-inline functions | Small object per capturing lambda (Kotlin 2.x uses invokedynamic) |
inline |
| Parameter null checks | Very cheap, JIT-optimized | -Xno-param-assertions (rarely worth it) |
by lazy { } |
Synchronized by default | LazyThreadSafetyMode.NONE |
companion object, defaults, object |
Extra synthetic classes | Negligible |
| Coroutines | Continuation allocations | Still far cheaper than OS threads |
| Stdlib | ~1.7 MB, some class loading | R8 on Android |
vararg / spread *array |
Array copy | Avoid on hot paths |
| Feature | Benefit |
|---|---|
inline functions |
No lambda objects for forEach, map, let, use |
value class |
Typed wrapper compiles to a primitive |
| Ranges | for (i in 0 until n) → plain int loop |
reified |
No Class passing or reflection |
| Coroutines vs. platform threads | Hundreds of bytes vs. ~1 MB stack each |
| Immutable data | Less defensive copying and locking |
| Java | Kotlin/JVM | |
|---|---|---|
| Startup | Baseline | A few ms more |
| JAR size | Baseline | +1.7 MB (+3 MB with kotlin-reflect) |
| Heap | Baseline | About the same |
| GraalVM native image | Supported | Supported (reflection may need config) |
| Android APK | Baseline | ~Equal after R8 |
Kotlin reflection is slower and heavier than Java reflection; it mostly affects startup.
kotlinx-benchmark, async-profiler, JFR.inline, avoid Int? in tight loops.