Learn Kotlin - Core Concepts & Architecture of the Kotlin Language
Episode 2 of 23

Learn Kotlin - Core Concepts & Architecture of the Kotlin Language

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.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

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.

Kotlin Program Structure

Files, Packages, and Top-Level Declarations

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:

Kotlinstruktur.kt
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 main Function as Entry Point

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:

KotlinMain.kt
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.

Compilation to Bytecode and JVM Interop

The Compilation Path

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:

Inspect bytecode with javap
kotlinc Main.kt
javap -c MainKt.class

The 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.

Interop with 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.

Type System and Null-Safety

Static Types with Type Inference

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:

KotlinType inference
val jumlah = 10
val pesan = "teks"
val harga = 19.99

The 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.

Separation of Nullable and Non-nullable Types

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.

Coroutines and the Concurrency Model

Moving from Threads to Coroutines

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.

Fundamentals to Be Used 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.

Architecture Summary

The concept map of this episode:

  • Structure: files, packages, functions, and classes; top-level declarations without a class wrapper.
  • Compilation: K2 produces JVM bytecode; directly interoperable with Java.
  • Types: a static type system with inference and nullable/non-nullable separation.
  • Concurrency: coroutines as the modern async model that suspends rather than blocks.

Closing

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:

  • Kotlin programs are composed of files, packages, functions, and classes; top-level declarations are normal.
  • Kotlin is compiled into JVM bytecode and is fully interoperable with Java.
  • Compiled class names for top-level declarations get the Kt suffix.
  • Type inference keeps code concise without losing static type safety.
  • Nullable and non-nullable types are separated at the type level.
  • Coroutines are the primary concurrency model in Kotlin.

In episode 3 we'll discuss Kotlin syntax and basic constructsval 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.