Lecture notes — Kotlin: What and Why

Published

2026-08-21 00:00

Keywords

ver. 1.0.0

← Kotlin: What and Why

Where we are

This is the first unit of the Kotlin module, so we start from the beginning: what Kotlin is, and why anyone built another language for a platform that already had Java.

The answer is not novelty. Kotlin’s claim is that a language can be concise, safe and pragmatic while remaining completely interoperable with the code you already have — and that the combination is worth more than any of the parts.

This unit is a survey of that claim. Almost nothing here is explained fully, which is deliberate. The point is to see the whole shape before learning any single part.

What you will be able to do

  1. read-a-first-kotlin-program — Read a short Kotlin program and identify the features that make it short.
  2. state-kotlins-primary-traits — State Kotlin’s primary traits and say who each one is for.
  3. explain-static-typing-with-inference — Explain how Kotlin keeps static typing while removing most type annotations.
  4. describe-kotlins-functional-side — Describe what Kotlin takes from functional programming and what it gains from it.
  5. place-kotlin-in-its-application-areas — Place Kotlin in its main application areas, including DSLs.
  6. state-the-design-philosophy — State the four philosophical commitments behind Kotlin’s design.
  7. compile-and-run-kotlin — Describe how Kotlin code is compiled and what it produces.

What we will cover

  • A first example — a data class and a collection query, read for shape rather than detail.
  • Static typing with inference — the guarantee kept, the ceremony removed.
  • Functional and object-oriented — the ingredients and the three benefits claimed.
  • Application areas — server-side, Android, and a first look at DSLs.
  • The philosophy — pragmatic, concise, safe, interoperable.
  • The toolchain — how a .kt file becomes something the JVM runs.

Why another JVM language

The JVM was not short of languages. Java itself, plus Groovy, Scala, Clojure, JRuby and Jython — the platform has hosted a great many attempts to be something other than Java.

Kotlin’s pitch is unusual because it is so modest. It does not introduce a new computational model. It does not ask you to abandon your libraries. It does not even ask you to abandon your existing code: a Kotlin file can be added to a Java project and compiled alongside it.

What it offers instead is that four things hold at the same time: static typing, conciseness, safety, and full interoperability. Each on its own is unremarkable. Together they are the whole argument.

Keep one thread in view for the whole unit: nearly every design decision in Kotlin traces back to interoperability. The language could have been safer, or more concise, or more purely functional, had it been willing to break from Java. It is not willing — and the compromises that follow are more interesting than a clean-sheet design would have been.

Learning outcomes

  • state-kotlins-primary-traits: State Kotlin’s primary traits and say who each one is for.

A first taste

The book opens with a program rather than a description, which is the right way in. Read this for its shape; do not try to understand every piece of the syntax yet.

data class Person(val name: String,
                  val age: Int? = null)

fun main(args: Array<String>) {
    val persons = listOf(Person("Alice"),
                         Person("Bob", age = 29))

    val oldest = persons.maxBy { it.age ?: 0 }
    println("The oldest is: $oldest")
}
The oldest is: Person(name=Bob, age=29)

That is a class, two objects, a query, and formatted output in nine lines. The equivalent Java runs to several dozen, most of it mechanical.

What is doing the work

Six features, each of which has a unit waiting for it later in the module:

  • the data classequals, hashCode and toString are generated from that one line, which is why println produces a readable result instead of Person@1b6d3586
  • default parameter valuesage defaults to null, so a Person can be constructed with one argument or two, with no overloads written by hand
  • type inference — the type of persons is never written down, but it is fully known: List<Person>
  • a lambda{ it.age ?: 0 } is a function passed as a value, and it is the implicit name of its single parameter
  • the Elvis operator?: supplies 0 when the age is absent
  • a string template$oldest embeds an expression directly into the string

And in the type itself: age: Int? says the age may be absent. name: String says the name may not. That distinction is enforced by the compiler, and it is the single feature Kotlin is best known for.

NoteWhat not to do here

Do not try to master this example. Its purpose is to make the later units feel like explanations of something you have already met, rather than a list of features arriving from nowhere.

Two things are worth registering even so. There is no boilerplate — no getters, no constructor body, no equals. And the query reads as a description of what is wanted rather than a loop over a list. Both recur throughout the module.

Learning outcomes

  • read-a-first-kotlin-program: Read a short Kotlin program and identify the features that make it short.

Concepts

  • data-classes: the single line that generates equals, hashCode and toString
  • nullable-types: an absent age is visible in the type, not hidden in the documentation
  • functional-programming: the query is a lambda passed to a collection function, not a loop

Statically typed without the noise

Kotlin is statically typed, like Java and unlike Groovy or JRuby. The type of every expression is known at compile time, and the compiler verifies that the methods and fields you use actually exist.

That is a familiar discipline. What is less familiar is not having to write it down.

val x = 1                  // Int, inferred
val name = "Kotlin"        // String, inferred
val list = listOf(1, 2, 3) // List<Int>, inferred

Every one of these is as strongly typed as a declaration with the type spelled out. Type inference determines the type from context; it does not weaken it.

What static typing buys

Worth being explicit about, because these benefits are usually left implicit:

  • performance — method calls are faster when there is no need to work out at runtime which method is being called
  • reliability — the compiler verifies consistency, so fewer failures surface at runtime
  • maintainability — unfamiliar code is easier to work with when you can see what types the objects are
  • tool support — reliable refactoring, precise completion, and everything else the IDE does follows from knowing the types

What it usually costs

In Java, you state a type every time you declare anything, even when the type is completely determined by the right-hand side. Map<String, List<Person>> m = new HashMap<String, List<Person>>(); says the same thing twice, and neither half is optional.

Inference removes the second half. The type is still there and still checked; it is simply not written down.

TipThe pattern to notice

This is the first instance of something that recurs throughout the language:

Kotlin does not remove a guarantee in order to gain convenience. It keeps the guarantee and removes the ceremony.

Watch for it in nullability, in smart casts, in data classes, in delegated properties. It is the closest thing Kotlin has to a single design principle.

Learning outcomes

  • explain-static-typing-with-inference: Explain how Kotlin keeps static typing while removing most type annotations.
  • state-kotlins-primary-traits: State Kotlin’s primary traits and say who each one is for.

Concepts

  • type-inference: the compiler determines the type from context, so the guarantee costs no keystrokes

Functional and object-oriented

Java is object-oriented, and Kotlin does not abandon that. You have classes, interfaces, inheritance and everything that comes with them.

What Kotlin adds is a full functional capability, so you choose the style that suits the problem rather than the style the language permits.

The three ingredients

  • First-class functions. Functions are values. Store one in a variable, pass it as an argument, return one from a function.
  • Immutability. Work with objects that are guaranteed not to change after they are created.
  • No side effects. Pure functions return the same result for the same inputs, and do not modify anything else.

The three benefits

Each follows from an ingredient, and each is a real claim rather than a slogan:

  • Conciseness. A function passed as a value can express what a whole block of code otherwise would. The maxBy { it.age ?: 0 } from the first example is the whole of a loop, an accumulator and a comparison.
  • Safe multithreading. Immutable data and pure functions cannot be corrupted by concurrent access, because there is nothing to corrupt. This eliminates a class of bug rather than helping you manage it.
  • Easier testing. A function with no side effects needs no setup and no mocks. Call it, check what comes back.

Three later units cash this cheque. Programming with Lambdas introduces the syntax and the collection API. Higher-Order Functions covers functions taking and returning functions, and the inlining that makes them cheap. DSL Construction, the last unit, uses lambdas with receivers to build APIs that read like languages.

Learning outcomes

  • describe-kotlins-functional-side: Describe what Kotlin takes from functional programming and what it gains from it.

Concepts

  • functional-programming: first-class functions, immutability and purity, and the three benefits Kotlin claims from them

Where Kotlin is used

Server-side development is Kotlin’s first and largest domain: web applications, backends, microservices. The advantage is not that Kotlin is better at HTTP. It is that a Kotlin service uses every existing Java framework and library unchanged, and can be introduced into a Java codebase one file at a time.

Android is where the benefits concentrate. Mobile applications are written under conditions that make Kotlin’s traits pay: less boilerplate matters when you are writing many small screens, and null safety matters a great deal when a NullPointerException is a crash the user sees.

Beyond the JVM, Kotlin also targets JavaScript and native compilation. This module stays on the JVM.

The DSL preview

The book shows two examples that deserve a second look. An HTML builder:

fun renderPersonList(persons: Collection<Person>) =
    createHTML().table {
        for (person in persons) {
            tr {
                td { +person.name }
                td { +person.age.toString() }
            }
        }
    }

And a database query:

Country.select { Country.name eq "Kotlin" }

The right reaction is to look twice. Both look like new syntax and neither is. There is no HTML mode in the compiler and no embedded SQL. Both are ordinary Kotlin — functions, lambdas and operator conventions — arranged so that the result reads like a specialised language.

Notice also what the first one does that a template engine cannot: it is a for loop inside markup, type-checked, with the IDE completing tag names.

ImportantThis is where the module is going

The last unit of this module builds a DSL, and the machinery it uses comes from units 3, 5, 7 and 8 — extension functions, lambdas, operator conventions, and lambdas with receivers.

None of those features was introduced for DSLs. That they compose into one is the argument the module is making.

Learning outcomes

  • place-kotlin-in-its-application-areas: Place Kotlin in its main application areas, including DSLs.

Concepts

  • domain-specific-languages: markup and queries that look like new syntax and are ordinary Kotlin
  • java-interoperability: why a Kotlin service can use every existing Java framework unchanged

The philosophy

Four commitments, and between them they explain nearly every design decision in the language.

Pragmatic

Kotlin is a practical language for solving real problems. It is not a research vehicle, and most of its features are ideas already proven elsewhere.

A consequence worth noting: Kotlin does not impose a particular style or architecture. It has strong opinions about syntax and almost none about how you organise a program.

Concise

Reading code takes more time than writing it, so removing ceremony is a genuine gain — the reader spends attention on intent rather than on scaffolding. Getters, setters, constructor parameter assignments and similar boilerplate are the compiler’s job.

But conciseness is not brevity for its own sake. Kotlin avoids cryptic operators, on the grounds that unfamiliar code should still be readable. Short is not the goal; clear is, and short is usually how you get there.

Safe

Here a tradeoff is being made explicitly: the compiler requires more information from you, and in exchange prevents more errors.

The headline case is nullability, tracked in the type system. A value that may be absent has a different type from one that cannot be:

val s1: String = null   // will not compile
val s2: String? = null  // fine

The compiler then refuses code that would produce a NullPointerException. This is worth appreciating for what it is: the most common runtime failure on the JVM, converted into a compile error.

Smart casts are the second example and the more elegant one:

if (value is String)      // check the type
    println(value.toUpperCase())   // now use it as a String, no cast

Java requires the check and then the cast — the same fact stated twice. Kotlin puts the information the compiler already has to work rather than demanding it a second time.

That is the same principle as type inference, applied to control flow instead of declarations.

Interoperable

Kotlin calls Java; Java calls Kotlin. Existing libraries work unchanged, and Kotlin’s collections are Java collections rather than wrappers around them.

The consequence is that Kotlin can enter an existing codebase incrementally, which is the only way most languages ever get adopted at all.

NoteThe cost of this commitment

Interoperability is not free, and the module will meet the bills as they come due:

  • platform types, in the type system unit — Java code carries no nullability information, so Kotlin has to admit a category of type about which it cannot make its usual promise
  • collection mutability, in the same unit — Kotlin’s read-only interfaces are a Kotlin-side view of a Java object that Java code can still modify

Both are compromises. Both are more instructive than the clean design would have been, because they show what a real language does when principle meets an installed base.

Learning outcomes

  • state-the-design-philosophy: State the four philosophical commitments behind Kotlin’s design.

Concepts

  • kotlin-philosophy: pragmatic, concise, safe, interoperable — and what each rules out
  • smart-casts: the compiler reuses what a type check already established, instead of asking again
  • nullable-types: absence tracked in the type system, so the compiler can refuse a null dereference

From source to bytecode

The mechanical section, and it matters more than it looks.

The Kotlin build process: .kt sources compile to .class files, packaged with the runtime library into a JAR.

Kotlin source files use the .kt extension. The kotlinc compiler produces .class files, which are packaged and executed exactly like the output of javac:

kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar

In practice you invoke this through Gradle, Maven or the IDE rather than by hand. Seeing the command-line form once makes the process concrete.

The runtime library. Compiled applications also depend on the Kotlin runtime, which supplies Kotlin’s own standard library classes and the extensions it adds to the standard Java APIs. It is small, and it ships with the application.

Why this section justifies the interoperability claim

Kotlin does not run in its own environment and does not interpret its own bytecode. It produces the same artefacts Java does. That single fact is why:

  • existing Java libraries work without adaptation
  • Java code can call Kotlin classes as though they were Java classes
  • a Kotlin file can be added to a Java project and compiled alongside it
  • existing JVM tooling — profilers, debuggers, build systems, application servers — keeps working

The interoperability commitment from the previous section is not aspirational. It falls out of the compilation model.

Learning outcomes

  • compile-and-run-kotlin: Describe how Kotlin code is compiled and what it produces.

Concepts

  • java-interoperability: the compilation model is what makes the interoperability claim credible

What you can now recognise

You have seen the whole shape: a short program and the features that make it short, the traits, the philosophy, and the toolchain underneath.

Nothing here was explained fully. Everything here returns.

Learning outcomes

  • read-a-first-kotlin-program: Read a short Kotlin program and identify the features that make it short.
  • state-kotlins-primary-traits: State Kotlin’s primary traits and say who each one is for.
  • explain-static-typing-with-inference: Explain how Kotlin keeps static typing while removing most type annotations.
  • describe-kotlins-functional-side: Describe what Kotlin takes from functional programming and what it gains from it.
  • place-kotlin-in-its-application-areas: Place Kotlin in its main application areas, including DSLs.
  • state-the-design-philosophy: State the four philosophical commitments behind Kotlin’s design.
  • compile-and-run-kotlin: Describe how Kotlin code is compiled and what it produces.

Conclusion

  • Kotlin’s claim is a conjunction, not a feature.

    Statically typed and concise and safe and interoperable. Any one of those is ordinary. Holding all four at once is the whole design problem, and the reason the language looks the way it does.

  • Static typing survives having almost no type annotations.

    Inference determines the type from context. val x = 1 is as strongly typed as any Java declaration — the guarantee is kept and the ceremony is dropped. That pattern recurs everywhere in the language.

  • Functional and object-oriented is not a compromise position.

    First-class functions, immutability and purity buy conciseness, thread safety by construction, and testability. Kotlin adds them without giving up classes and interfaces, so you choose per problem.

  • Safety means the type system, not runtime checks.

    Nullability lives in the type: String and String? are different types, and the compiler refuses the dereference that would fail. Smart casts are the same idea applied to control flow.

  • Interoperability explains the compromises as well as the strengths.

    Kotlin compiles to ordinary bytecode, which is why Java libraries work unchanged. It is also why platform types and collection mutability exist, and those are worth watching for when we reach them.

  • The DSL examples in this unit are ordinary Kotlin.

    No new syntax, no macros, no runtime parsing. Small composable features — extensions, lambdas, conventions — arranged to read like a language. That is where this module ends up.

Where next

The next unit, Kotlin Basics, stops surveying and starts teaching: functions and variables, classes and properties, when in place of switch, smart casts in earnest, loops and ranges, and exceptions.

By the end of it you will be able to write small programs rather than only read them.