This episode breaks down Kotlin's architecture: program structure composed of files, packages, functions, and classes; the compilation process into JVM bytecode; the type system with null-safety; and an overview of coroutines and the modern concurrency model.

Now that the environment is ready in episode 0 and the motivation is clear in episode 1, it's time to understand how Kotlin works from the inside. Episode 2 breaks down the language's architecture: how programs are organized, how code is translated into JVM bytecode, how the type system enforces safety, and how concurrency is handled.
Understanding this architecture isn't just theory. When you face strange errors in production, understanding the compilation flow and the type model will help you guess the root cause much faster. It also builds the mental model needed for episodes 3 through 9.
By the end of this episode you'll be able to explain the life cycle of a Kotlin program: from source file, through the compiler, into bytecode, and up to execution by the JVM.
A Kotlin program is composed of .kt files grouped into packages. Unlike Java, Kotlin allows top-level declarations such as functions and properties directly at the file level, without having to wrap them in a class:
package id.devnull.contoh
fun sapa(nama: String): String = "Halo, $nama"
val versi = "1.0"The sapa function and versi property above live at the file level and can be called directly from other files in the same package. The file directory doesn't have to match the package — Kotlin isn't as strict as Java in this regard, although convention recommends keeping them aligned.
The entry point of execution is the main function. With Kotlin 2.x, you can write it without parameters, or with a String array parameter:
fun main(args: Array<String>) {
println("Jumlah argumen: ${args.size}")
}If you run this program via kotlinc Main.kt -include-runtime -d main.jar and then java -jar main.jar, the main function will be executed by the JVM. String templates with ${args.size} are an example of expressions that will be covered in episode 3.
Kotlin isn't interpreted like a scripting language — it's compiled. The K2 compiler translates .kt sources into .class files containing JVM bytecode. The flow in brief: source files are processed by K2, bytecode is produced, then executed by the JVM together with the Kotlin runtime library.
To see the compiled bytecode, you can use a JDK built-in tool:
kotlinc Main.kt
javap -c MainKt.classThe javap -c MainKt.class command displays the bytecode instructions of the compiled class. The class is named MainKt with the Kt suffix because of top-level declarations — a small detail that often surprises developers newly migrated from Java.
Because the output format is the same (JVM bytecode), Kotlin and Java can call each other directly. Java libraries like SLF4J, Jackson, or Spring can be used without adapters, and conversely Kotlin classes look like ordinary Java classes to callers on the Java side. Episode 17 will cover this interop bridge in depth.
Kotlin is a statically typed language: the type of every expression is known at compile time. However, Kotlin applies type inference, so you rarely write types explicitly:
val jumlah = 10
val pesan = "teks"
val harga = 19.99The jumlah variable is inferred as Int, pesan as String, and harga as Double. The compiler still checks every operation against the known types, so type safety isn't reduced even though the syntax is concise.
The most important architectural feature: Kotlin separates nullable and non-nullable types at the type level. A String variable can never hold null; for that you need String?. The compiler forces you to handle the possibility of null, so most NullPointerExceptions are detected at compile time. Full details are covered in episode 6.
Kotlin introduces coroutines as the primary model for concurrency and asynchrony. Unlike threads, which are expensive to create, a coroutine is a lightweight thread that can run millions of instances on a single physical thread. Operations like network calls don't block a thread; instead, they suspend execution and resume it later.
The core concept to understand now: a suspend function is a function that can suspend its execution without blocking a thread. Coroutine scopes, dispatchers, and their control structures will be covered thoroughly in episode 9. For now, just remember that coroutines are the async foundation in Kotlin — on both Android and backend.
The concept map of this episode:
Episode 2 gives you the blueprint of how Kotlin works: how programs are organized into files and packages, how compilation produces bytecode interoperable with Java, how the type system enforces safety, and how coroutines handle concurrency without pain.
The key takeaways:
In episode 3 we'll discuss Kotlin syntax and basic constructs — val and var variables, data types, string templates, control flow with if and when, functions with default arguments, and extension functions and infix notation. This is the syntax toolkit you'll use in every following episode.