cv.cm

Coding · Programming languages

Kotlin tutorial for beginners

7 min read

Short answer

Kotlin is a modern, statically typed language from JetBrains that runs on the JVM and is the preferred language for Android development. It is concise, fully interoperable with Java, and its type system prevents most null pointer errors at compile time. This page covers setup, variables, null safety, control flow, functions, collections, classes, coroutines and error handling with runnable examples.

What Kotlin is and what it is used for

Kotlin is a statically typed, general-purpose language created by JetBrains. It runs on the Java Virtual Machine (JVM), and it can also compile to JavaScript, WebAssembly and native machine code. Kotlin is fully interoperable with Java: a Kotlin file can call any Java library, and Java code can call Kotlin.

Google has recommended Kotlin as the preferred language for Android apps since 2019, so most new Android code is Kotlin. It is also common on the server with frameworks such as Spring Boot and Ktor, and Kotlin Multiplatform lets teams share business logic between Android, iOS, desktop and web.

Install Kotlin and run Hello, world

The easiest start is IntelliJ IDEA or Android Studio, which include Kotlin. For the command line, install the compiler (for example with SDKMAN! or Homebrew) and a Java runtime (JDK). A Kotlin program starts at a top-level main function; no class is required.

// hello.kt
fun main() {
    println("Hello, world!")
}
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar
# Hello, world!

Build tools such as Gradle (with the Kotlin DSL) are the normal choice for real projects. You can also try code without installing anything in the online Kotlin Playground.

Variables and basic types

Declare a read-only variable with val and a mutable one with var. The compiler infers the type, but you can write it after a colon. Prefer val: it makes code easier to reason about.

val name = "Ada"          // String, cannot be reassigned
var count: Int = 0        // explicit type, can change
count += 1
val pi = 3.14159          // Double
val big = 9_000_000_000L  // Long
println("$name has $count item(s), pi is ${"%.2f".format(pi)}")
// Ada has 1 item(s), pi is 3.14
TypeExampleNotes
Int4232-bit signed integer
Long42L64-bit signed integer
Double3.1464-bit floating point
Booleantruetrue or false
Char'k'a single character
String"text"immutable; supports $name templates

Kotlin has no implicit number widening: an Int is not automatically converted to a Long. Call toLong(), toDouble() or toString() explicitly.

Null safety

Null safety is Kotlin's signature feature. A normal type such as String can never hold null. To allow null you add ?, as in String?, and the compiler then forces you to handle the null case before you use the value.

fun greet(name: String?) {
    // println(name.length)        // compile error: name may be null
    println(name?.length)           // safe call: prints null if name is null
    val len = name?.length ?: 0     // Elvis operator: default value
    if (name != null) {
        println(name.uppercase())   // smart cast: name is String here
    }
    println("length = $len")
}
fun main() {
    greet("kotlin")   // 6, KOTLIN, length = 6
    greet(null)       // null, length = 0
}

Control flow: if, when and loops

In Kotlin if and when are expressions, so they return a value. when replaces the switch statement of Java and C and can match values, ranges and types.

val score = 82
val grade = if (score >= 50) "pass" else "fail"
val letter = when (score) {
    in 90..100 -> "A"
    in 80..89 -> "B"
    in 70..79 -> "C"
    else -> "F"
}
println("$grade $letter")         // pass B
for (i in 1..3) print("$i ")      // 1 2 3
println()
for (i in 10 downTo 0 step 5) print("$i ")  // 10 5 0
println()
var n = 3
while (n > 0) { print(n); n-- }   // 321
println()

1..3 is a closed range that includes both ends; 0 until 3 (or 0..<3) excludes the end, which is what you usually want for indexes.

Functions and lambdas

Functions are declared with fun. Parameters can have default values, callers can use named arguments, and a function whose body is one expression can use =.

fun area(width: Double, height: Double = 1.0): Double = width * height
fun main() {
    println(area(3.0, 2.0))          // 6.0
    println(area(width = 4.0))       // 4.0 (default height)
    val square = { x: Int -> x * x } // a lambda stored in a variable
    println(square(5))               // 25
    val nums = listOf(1, 2, 3, 4, 5)
    val evensSquared = nums.filter { it % 2 == 0 }.map { it * it }
    println(evensSquared)            // [4, 16]
}

When a lambda has one parameter, you can refer to it as it. When the last argument of a function is a lambda, it can go outside the parentheses, which is why filter { ... } reads like a built-in block.

Collections and data classes

The standard library separates read-only and mutable collections: listOf, setOf and mapOf create read-only ones, while mutableListOf, mutableSetOf and mutableMapOf can be changed.

val fruits = mutableListOf("apple", "banana")
fruits.add("cherry")
val ages = mapOf("Ann" to 31, "Bo" to 25)
println(fruits.size)              // 3
println(ages["Ann"])              // 31
println(ages["Zed"])              // null (map lookups are nullable)
data class User(val name: String, val age: Int)
val u = User("Ann", 31)
val older = u.copy(age = 32)
println(u)                        // User(name=Ann, age=31)
println(u == User("Ann", 31))     // true (structural equality)
val (n, a) = older                // destructuring
println("$n $a")                  // Ann 32

A data class automatically gets equals, hashCode, toString, copy and destructuring. == in Kotlin compares content (it calls equals); === compares references.

Classes, extension functions and coroutines

Classes are final by default; mark a class open to allow inheritance. Extension functions add methods to existing types without changing them, and sealed classes model a closed set of cases that when can check exhaustively.

fun String.shout(): String = uppercase() + "!"
sealed class Shape
data class Circle(val r: Double) : Shape()
data class Rect(val w: Double, val h: Double) : Shape()
fun area(s: Shape): Double = when (s) {
    is Circle -> Math.PI * s.r * s.r
    is Rect -> s.w * s.h
}   // no else needed: the compiler knows all cases
fun main() {
    println("hi".shout())                   // HI!
    println(area(Rect(2.0, 3.0)))           // 6.0
}

For asynchronous and concurrent code, Kotlin uses coroutines from the kotlinx.coroutines library. A suspend function can pause without blocking a thread, and builders such as launch and async start coroutines inside a scope. This is how Android apps do network calls off the main thread.

Error handling

Kotlin has try, catch and finally like Java, but all exceptions are unchecked: you are never forced to declare or catch them. try is also an expression. For expected failures, the standard library offers runCatching and Result, and functions such as toIntOrNull() return null instead of throwing.

fun parse(s: String): Int = try {
    s.toInt()
} catch (e: NumberFormatException) {
    -1
}
fun main() {
    println(parse("42"))              // 42
    println(parse("x"))               // -1
    println("7".toIntOrNull())        // 7
    println("seven".toIntOrNull())    // null
    val r = runCatching { "abc".toInt() }
    println(r.isFailure)              // true
}

A small complete program: word frequency

This program counts how often each word appears in a text and prints the top three. It uses a string extension, collection functions and a data class.

data class WordCount(val word: String, val count: Int)
fun topWords(text: String, n: Int): List<WordCount> =
    text.lowercase()
        .split(Regex("[^a-z']+"))
        .filter { it.isNotBlank() }
        .groupingBy { it }
        .eachCount()
        .map { (w, c) -> WordCount(w, c) }
        .sortedWith(compareByDescending<WordCount> { it.count }.thenBy { it.word })
        .take(n)
fun main() {
    val text = "The cat and the dog. The dog sat, and the cat ran."
    for ((word, count) in topWords(text, 3)) {
        println("$word: $count")
    }
}
// the: 4
// and: 2
// cat: 2

Where to go next

  • The official Kotlin documentation at kotlinlang.org, starting with Basic syntax and the Kotlin tour.
  • Kotlin Koans, a set of small exercises that run in the browser.
  • The Android Developers guides for Kotlin on Android, or the Ktor and Spring guides for server-side Kotlin.
  • The kotlinx.coroutines guide once you need asynchronous code.

FAQ

Is Kotlin better than Java?

Kotlin is more concise and adds null safety, data classes and coroutines, but it runs on the same JVM and uses the same libraries. Many teams mix both languages in one project, so the choice is about style and productivity rather than capability.

Do I need to learn Java before Kotlin?

No. You can learn Kotlin as a first JVM language. Knowing some Java helps when you read older Android code or Java library documentation.

What is the difference between val and var?

val declares a read-only reference that cannot be reassigned after initialization, and var declares one that can. A val pointing to a mutable list can still have its contents changed.

Can Kotlin be used for iOS apps?

Yes, through Kotlin Multiplatform, which compiles shared Kotlin code to native code for iOS. The user interface is often still written in Swift, although Compose Multiplatform can also share UI code.

What does the ?: operator do in Kotlin?

It is the Elvis operator: a ?: b returns a if it is not null and otherwise returns b. It is commonly combined with safe calls, as in name?.length ?: 0.

All coding tutorials