Advanced Kotlin: Inline, Reified, DSLs & Sealed Types

📖 Concept

Advanced Kotlin features are what separate a senior Kotlin developer from someone who just writes "Java in Kotlin." These features enable type-safe DSLs, zero-overhead abstractions, and exhaustive state modeling.

Inline Functions: The compiler copies the function body to the call site, eliminating the overhead of function calls and lambda object creation. Critical for high-order functions used in hot paths.

Reified Type Parameters: Only available in inline functions. The type parameter is available at runtime (unlike regular generics which are erased). Enables is T checks, T::class references, and inline function-specific APIs.

DSL Building: Kotlin's extension functions + lambda with receiver + @DslMarker create type-safe domain-specific languages. Used in Compose, Gradle build scripts, Ktor routing, and more.

Sealed Classes/Interfaces: Model restricted class hierarchies. The compiler knows all subtypes, enabling exhaustive when expressions. Essential for modeling UI state, results, and navigation events.

Value Classes (inline classes): Wrapper types with zero runtime overhead. The compiler removes the wrapper at runtime. Use for type-safe wrappers around primitive types.

💻 Code Example

codeTap to expand ⛶
1// Inline function — eliminates lambda allocation overhead
2inline fun <T> measureTime(block: () -> T): Pair<T, Long> {
3 val start = System.nanoTime()
4 val result = block() // Inlined at call site, no lambda object created
5 val duration = (System.nanoTime() - start) / 1_000_000
6 return result to duration
7}
8
9// Reified — type parameter available at runtime
10inline fun <reified T> Intent.getParcelableExtraCompat(key: String): T? {
11 return if (Build.VERSION.SDK_INT >= 33) {
12 getParcelableExtra(key, T::class.java)
13 } else {
14 @Suppress("DEPRECATION")
15 getParcelableExtra(key) as? T
16 }
17}
18
19// Usage: val user = intent.getParcelableExtraCompat<User>("user")
20// Without reified, you'd need to pass Class<T> explicitly
21
22// DSL Building — type-safe builders
23@DslMarker
24annotation class NetworkDsl
25
26@NetworkDsl
27class RequestBuilder {
28 var url: String = ""
29 var method: String = "GET"
30 private val headers = mutableMapOf<String, String>()
31 private var body: String? = null
32
33 fun headers(block: HeaderBuilder.() -> Unit) {
34 HeaderBuilder(headers).apply(block)
35 }
36 fun body(content: String) { body = content }
37 fun build() = Request(url, method, headers, body)
38}
39
40@NetworkDsl
41class HeaderBuilder(private val headers: MutableMap<String, String>) {
42 infix fun String.to(value: String) { headers[this] = value }
43}
44
45fun request(block: RequestBuilder.() -> Unit): Request {
46 return RequestBuilder().apply(block).build()
47}
48
49// Usage — reads like natural language
50val req = request {
51 url = "https://api.example.com/users"
52 method = "POST"
53 headers {
54 "Authorization" to "Bearer token123"
55 "Content-Type" to "application/json"
56 }
57 body("""{"name": "Alice"}""")
58}
59
60// Sealed interface — exhaustive state modeling
61sealed interface NetworkState<out T> {
62 data object Idle : NetworkState<Nothing>
63 data object Loading : NetworkState<Nothing>
64 data class Success<T>(val data: T) : NetworkState<T>
65 sealed interface Error : NetworkState<Nothing> {
66 data class Network(val message: String) : Error
67 data class Server(val code: Int, val message: String) : Error
68 data class Auth(val reason: String) : Error
69 }
70}
71
72// Exhaustive when — compiler ensures all cases handled
73fun <T> handleState(state: NetworkState<T>) = when (state) {
74 is NetworkState.Idle -> showIdle()
75 is NetworkState.Loading -> showSpinner()
76 is NetworkState.Success -> showData(state.data)
77 is NetworkState.Error.Network -> showRetry(state.message)
78 is NetworkState.Error.Server -> showServerError(state.code)
79 is NetworkState.Error.Auth -> navigateToLogin()
80 // No 'else' needed — compiler verifies exhaustiveness
81}
82
83// Value class — zero overhead type safety
84@JvmInline
85value class UserId(val value: String)
86
87@JvmInline
88value class Email(val value: String) {
89 init { require(value.contains("@")) { "Invalid email" } }
90}
91
92fun getUser(id: UserId): User { /* ... */ }
93// getUser(UserId("123")) ← clear intent
94// getUser("123") ← compile error! Type safety without runtime cost

🏋️ Practice Exercise

Practice:

  1. Create an inline function with a reified type parameter for safe JSON parsing
  2. Build a type-safe DSL for constructing SQL queries
  3. Model a complete e-commerce order state machine using sealed interfaces
  4. Create value classes for all your domain identifiers (UserId, OrderId, ProductId)
  5. Explain what @DslMarker does and why it's important for nested DSLs

⚠️ Common Mistakes

  • Overusing inline — only inline functions that take lambdas as parameters or use reified types. Inlining large functions increases bytecode size.

  • Not using sealed classes for state — using enums or strings for states loses type safety and exhaustive checks

  • Forgetting that reified only works with inline functions — the type must be known at the call site

  • Using data class for value semantics when value class suffices — value classes have zero runtime overhead

  • Not using @DslMarker — without it, nested DSL blocks can access parent scope's methods, creating confusing code

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Advanced Kotlin: Inline, Reified, DSLs & Sealed Types. Login to unlock this feature.