QUICK START:HTMLCSSJavaScriptTypeScriptPythonSQLReactNext.jsNode.jsLinux & UbuntuKotlinSwiftC# / .NETJavaGoRustC++DSASystem DesignDevOpsCybersecurityAI / ML
Advanced 26 min readModule: Module 13: Coroutine Machinery: CPS Transformation & State Machines

Coroutine Mechanics: CPS Transformation & Bytecode Internals

Explore how the Kotlin compiler compiles `suspend` functions: Continuation-Passing Style (CPS) transformation, passing hidden `Continuation<T>` parameters, compiler-generated switch/case state machines, and label-based suspension resumes.

What You Will Learn in This Lesson

  • How `suspend fun calculate(): Int` is transformed into `fun calculate(continuation: Continuation<Int>): Any?`
  • The compiler-generated `CoroutineImpl` anonymous subclass and its `label` counter state machine
  • Why `COROUTINE_SUSPENDED` sentinel marker signals asynchronous suspension to the caller
  • Memory lifecycle of continuation stack frames on the JVM heap

Introduction & Core Concept

Kotlin coroutines appear to execute sequential, non-blocking code without callbacks. However, the JVM has no built-in concept of Kotlin suspension. The Kotlin compiler performs Continuation-Passing Style (CPS) transformation at compile time: every `suspend` function is rewritten with an extra hidden parameter (`Continuation`), and its body is converted into a finite state machine with numerical labels.
WHY DOES THIS MATTER IN THE REAL WORLD?

Understanding the CPS state machine allows you to debug coroutine stack traces, eliminate unnecessary suspension points in hot loops, and write low-level asynchronous integrations.

Syntax & Structure

kotlin
// Source code
suspend fun load(): Data = ...
// Decompiled JVM Bytecode equivalent
fun load(completion: Continuation<Data>): Any? { ... }

Simulating the Kotlin Compiler's CPS State Machine Decompilation

kotlin
kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// Simulation of Kotlin Compiler's CPS State Machine Decompilation
package com.kwasacademy.coroutines.internals
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
// 1. Conceptual Decompiled State Machine for:
// suspend fun fetchUserWorkflow(id: String): String {
// val profile = fetchProfile(id) // Suspension Point 1 (label 0 -> 1)
// val settings = fetchSettings(id) // Suspension Point 2 (label 1 -> 2)
// return "$profile with $settings"
// }
class FetchUserWorkflowStateMachine(
private val completion: Continuation<String>
) : Continuation<Any?> {
override val context: CoroutineContext = EmptyCoroutineContext
var label: Int = 0
var result: Any? = null
var savedProfile: String? = null
override fun resumeWith(result: Result<Any?>) {
this.result = result.getOrNull()
executeStateMachine()
}
fun executeStateMachine(): Any? {
when (label) {
0 -> {
println("[STATE MACHINE] Label 0: Initiating fetchProfile()")
label = 1
// Simulating suspend call returning COROUTINE_SUSPENDED
return COROUTINE_SUSPENDED
}
1 -> {
savedProfile = result as String
println("[STATE MACHINE] Label 1: Profile received '${savedProfile}', initiating fetchSettings()")
label = 2
return COROUTINE_SUSPENDED
}
2 -> {
val settings = result as String
val finalOutput = "${savedProfile} + ${settings}"
println("[STATE MACHINE] Label 2: Completed -> ${finalOutput}")
completion.resumeWith(Result.success(finalOutput))
return finalOutput
}
else -> throw IllegalStateException("Invalid coroutine state")
}
}
}
fun main() {
println("=== Kotlin Coroutines: CPS State Machine Execution ===")
val stateMachine = FetchUserWorkflowStateMachine(object : Continuation<String> {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resumeWith(result: Result<String>) {
println("✅ Final Coroutine Result Received: ${result.getOrNull()}")
}
})
// Step 1: Initial call
stateMachine.executeStateMachine()
// Step 2: Background I/O completes profile fetch
stateMachine.resumeWith(Result.success("UserProfile(Alex)"))
// Step 3: Background I/O completes settings fetch
stateMachine.resumeWith(Result.success("UserSettings(DarkMode)"))
}

Line-by-Line Technical Breakdown

1Continuation Frame Allocation: A single small state machine object is allocated on the heap when the coroutine begins. In sequential suspend chains, this single object is reused across all suspension points, minimizing heap allocations.

Try It Yourself (Interactive Editor)

Modify the code in real-time and click Run to test live browser output and console logs.

Intelligent Code Runner & Live Sandbox[KOTLIN]
KOTLIN SOURCE EDITOR
Interactive Live Code

Common Mistakes & How to Avoid Them

#1: Marking functions as `suspend` when they do not actually call any suspending functions, creating useless state machine bytecode.

The `suspend` keyword forces the compiler to generate state machine boilerplate. Only add `suspend` if the function actually suspends.

Incorrect / Antipattern
suspend fun add(a: Int, b: Int): Int = a + b // Useless suspend modifier
Correct / Professional Solution
fun add(a: Int, b: Int): Int = a + b

Industry Best Practices & Professional Standards

  • Do not add `suspend` modifier to purely synchronous, non-suspending calculations.
  • Use `inline` on higher-order suspending functions to eliminate lambda object allocations.
  • Inspect compiled bytecode using IntelliJ's 'Show Kotlin Bytecode' -> 'Decompile' tool.

Lesson Summary & Core Takeaways

  • Kotlin compiles coroutines into Continuation-Passing Style (CPS) state machines.
  • The `label` field tracks execution progress across suspension points.
  • `COROUTINE_SUSPENDED` signals non-blocking suspension to the calling thread.