A language tour
Built by toolmakers at JetBrains — a language that fixes Java's papercuts while keeping everything that made Java work.
01 — Null Safety
Kotlin eliminates the billion-dollar mistake of null references. Types are non-nullable by default — null becomes a conscious choice, marked explicitly, handled at the point where it might occur.
"I call it my billion-dollar mistake. It was the invention of the null reference in 1960."
— Sir Tony Hoare, inventor of null references// Non-nullable by default var name: String = "Kotlin" // Cannot be null // name = null // Compile error! // Explicit nullable types var maybeName: String? = "Hello" maybeName = null // This is allowed // Safe calls — no more NPEs val length = maybeName?.length // Returns null if maybeName is null // Elvis operator — provide defaults val displayName = maybeName ?: "Unknown" // "Unknown" if null // Non-null assertion (use carefully!) val sureName = maybeName!! // Throws NPE if null
The ?. safe call returns null instead of crashing — and the compiler forces you to handle that possibility before it reaches production.
02 — Conciseness
Kotlin cuts the boilerplate. Data classes, type inference, and smart defaults let you focus on what matters.
"A data class that takes 50 lines in Java takes one in Kotlin. That's not a shortcut — it's a reappraisal of what the programmer's job actually is."
— The Kotlin design philosophy// Data class — getters, setters, equals, hashCode, toString for free data class User(val name: String, val age: Int) val user = User("Alice", 30) println(user) // User(name=Alice, age=30) // Type inference val message = "Hello" // String inferred val numbers = listOf(1, 2, 3) // List<Int> inferred // When expressions replace switch fun describe(obj: Any): String = when (obj) { 1 -> "One" "Hello" -> "Greeting" is Long -> "Long number" else -> "Unknown" } // Single-expression functions fun double(x: Int) = x * 2
A data class generates equals, hashCode, toString, copy, and destructuring declarations — all from one line.
03 — Functional Programming
Kotlin embraces functional programming with lambdas, higher-order functions, and immutability — but keeps it practical.
// Lambdas and higher-order functions val numbers = listOf(1, 2, 3, 4, 5) val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10] val evens = numbers.filter { it % 2 == 0 } // [2, 4] val sum = numbers.fold(0) { acc, n -> acc + n } // 15 // Function types val operation: (Int, Int) -> Int = { a, b -> a + b } val result = operation(3, 4) // 7 // Inline functions for performance inline fun measureTime(block: () -> Unit) { val start = System.currentTimeMillis() block() val end = System.currentTimeMillis() println("Time: ${end - start}ms") } measureTime { println("Doing work...") }
it is the implicit parameter for single-parameter lambdas — less typing, same meaning.
04 — Interoperability
Kotlin compiles to JVM bytecode and calls Java code naturally. No wrappers, no impedance mismatch — just works.
// Call Java code directly import java.util.* val list = ArrayList<String>() // Java ArrayList list.add("Kotlin") list.add("Java") // Kotlin collections to Java val kotlinList = listOf("A", "B", "C") val javaList: List<String> = kotlinList // Extension functions on Java classes fun String.isPalindrome(): Boolean = this == this.reversed() println("racecar".isPalindrome()) // true // Java methods that return null are safely wrapped val homePath: String? = System.getProperty("user.home") // Returns null if unset val length = homePath?.length ?: 0
Extension functions let Kotlin add methods to Java's String, List, and any other class — without subclassing, without wrappers.
05 — Coroutines
Kotlin's coroutines make asynchronous programming feel like synchronous code. No callbacks, no promises — just sequential logic.
import kotlinx.coroutines.* suspend fun fetchUser(): String { delay(1000L) // Non-blocking pause — no thread is held return "Alice" } suspend fun fetchPosts(): List<String> { delay(800L) return listOf("Post 1", "Post 2") } fun main() = runBlocking { // Sequential: 1800ms total — each waits for the previous val user1 = fetchUser() val posts1 = fetchPosts() // Concurrent: 1000ms total — both run in parallel val user = async { fetchUser() } val posts = async { fetchPosts() } println("User: ${user.await()}") println("Posts: ${posts.await()}") }
async { } launches two coroutines simultaneously — await() collects them. Both network calls overlap, with no threads blocked during either delay.
06 — The Whole Picture
Algebraic data types for exhaustive when expressions — the compiler ensures you handle all cases.
After is checks, types are automatically cast — no explicit casting needed.
One language for JVM, JavaScript, and native — share code across platforms.
Zero-cost abstractions for types like meters vs feet — type safety without runtime overhead.
Define +, -, etc. for your types — make domain-specific operators.
In 2017, Google endorsed Kotlin for Android. JetBrains had spent six years making the case that language quality matters.