Kotlin Advanced

🚀 Kotlin Introduction

Kotlin is a modern, statically-typed programming language that runs on the Java Virtual Machine (JVM) and can be compiled to JavaScript or native code. Developed by JetBrains and officially supported by Google for Android development, Kotlin combines object-oriented and functional programming features in a concise, safe, and interoperable language.

Modern Language Design

Kotlin was designed from the ground up to address common programming pitfalls while maintaining full interoperability with Java. Its syntax is clean and expressive, reducing boilerplate code significantly compared to Java. The language includes modern features like type inference, extension functions, and smart casts that make development more productive and enjoyable.

JVM Interoperability

One of Kotlin's greatest strengths is its seamless interoperability with Java. You can call Java code from Kotlin and vice versa without any wrappers or special adapters. This allows gradual migration of Java projects to Kotlin and enables developers to leverage existing Java libraries and frameworks. The interoperability extends to the build tools, testing frameworks, and deployment processes familiar to Java developers.

Multi-Platform Capabilities

Kotlin is not limited to the JVM - it can compile to JavaScript for web development and to native code for iOS, macOS, Windows, and Linux applications through Kotlin/Native. Kotlin Multiplatform allows sharing business logic across Android, iOS, web, and desktop applications, significantly reducing development time and ensuring consistency across platforms.

Safety Features

Kotlin eliminates entire categories of errors through its design. The most notable feature is null safety, which distinguishes nullable and non-nullable types at the type system level. This prevents null pointer exceptions, one of the most common runtime errors in Java. Other safety features include checked exceptions (optional), immutable collections by default, and explicit type conversions.

Conciseness and Expressiveness

Kotlin reduces boilerplate code dramatically. Features like data classes, type inference, default parameters, and string templates make code more concise while remaining readable. A single line of Kotlin can often express what takes multiple lines in Java. This conciseness leads to fewer bugs, as there's less code to write and maintain.

Corporate Adoption

Since Google announced official support for Kotlin on Android in 2017, adoption has skyrocketed. Major companies like Google, Amazon, Netflix, Uber, Trello, and Pinterest use Kotlin in production. The language is also gaining traction in backend development with frameworks like Ktor and Spring Boot supporting Kotlin natively.

Learning Curve and Community

Kotlin has a gentle learning curve for Java developers, with most concepts being familiar but improved. The language documentation is excellent, and the community is growing rapidly with active support from JetBrains and Google. Kotlin's pragmatic design philosophy focuses on solving real-world problems effectively.

// Kotlin Introduction Example fun main() { println("Kotlin Advanced Track Loaded ⚡") // Basic syntax examples val name = "CodeOrbitPro" // Immutable variable var counter = 0 // Mutable variable // String template println("Welcome to $name's Kotlin Track!") // Function call val result = calculateSum(10, 20) println("Sum: $result") // Null safety example val nullableString: String? = null val length = nullableString?.length ?: 0 // Safe call with Elvis operator println("Length: $length") // Collection operations val numbers = listOf(1, 2, 3, 4, 5) val squares = numbers.map { it * it } println("Squares: $squares") } // Function definition fun calculateSum(a: Int, b: Int): Int { return a + b } // Data class (replaces Java POJOs) data class Person(val name: String, val age: Int) // Extension function fun String.isPalindrome(): Boolean { return this == this.reversed() } // Coroutine example (simplified) suspend fun fetchData(): String { // Simulate network call kotlinx.coroutines.delay(1000) return "Data loaded" }

✨ Kotlin Basics

Kotlin Basics encompass the fundamental building blocks of the language that every developer must master. These include variable declarations, basic types, functions, control flow structures, and the revolutionary null safety system. Kotlin's approach to these basics eliminates many common programming errors while maintaining expressiveness and readability.

Type System and Inference

Kotlin is a statically-typed language with sophisticated type inference. The compiler can deduce types from context, reducing verbosity while maintaining type safety. Kotlin distinguishes between nullable and non-nullable types at compile time, preventing null pointer exceptions. The type system includes all the expected primitive types (handled as objects) and introduces special types like Unit (similar to void) and Nothing (for functions that never return).

Immutable by Default

Kotlin promotes immutability through its `val` keyword for read-only variables. This functional programming principle makes code more predictable and thread-safe. Mutable variables require explicit declaration with `var`. This design choice encourages developers to think about state changes and often leads to more robust, maintainable code with fewer side effects.

Function Declarations

Functions in Kotlin are declared with the `fun` keyword and support default parameters, named arguments, and single-expression syntax. Kotlin functions can be top-level (outside classes), member functions (inside classes), local functions (inside other functions), or extension functions (adding functionality to existing types). This flexibility allows for clean, organized code structures.

String Templates and Raw Strings

Kotlin's string interpolation is more powerful than Java's, allowing arbitrary expressions within `${}`. Triple-quoted strings support multi-line text without escape sequences, making them ideal for JSON, SQL, or regex patterns. String templates reduce concatenation clutter and improve readability significantly.

Control Flow Enhancements

Kotlin enhances traditional control flow with expression-oriented design. `if`, `when`, and `try` can all return values, eliminating the need for ternary operators. The `when` expression is a powerful replacement for switch statements, supporting arbitrary conditions, type checks, and range matching. These features make control flow more expressive and less error-prone.

Scope Functions

Kotlin provides five scope functions (`let`, `run`, `with`, `apply`, and `also`) that execute code blocks in the context of an object. These functions enable clean, fluent APIs and reduce temporary variable declarations. Each scope function has subtle differences in receiver (`this` vs `it`) and return value (context object vs lambda result) that suit different use cases.

Basic Collections

Kotlin distinguishes between mutable and immutable collections at the interface level. The standard library provides `List`, `Set`, and `Map` in both mutable and immutable variants. Collection operations are rich and functional-oriented, with methods like `filter`, `map`, `reduce`, and `fold` available out of the box.

// Kotlin Basics Examples fun main() { // 1. Variables and Types val immutable = "Cannot be reassigned" // Read-only var mutable = "Can be changed" // Mutable mutable = "New value" // Type inference val inferredString = "Hello" // Compiler knows it's String val inferredInt = 42 // Compiler knows it's Int // Explicit types val explicitDouble: Double = 3.14 val explicitList: List = listOf("a", "b", "c") // 2. Basic Types val boolean: Boolean = true val char: Char = 'K' val byte: Byte = 127 val short: Short = 32767 val int: Int = 2147483647 val long: Long = 9223372036854775807L val float: Float = 3.14f val double: Double = 3.141592653589793 // 3. Strings and Templates val name = "Kotlin" val greeting = "Hello, $name!" // String interpolation val lengthMessage = "$name has ${name.length} characters" // Multi-line string val json = """ { "name": "$name", "version": "1.8", "features": ["null-safety", "coroutines", "dsl"] } """.trimIndent() // 4. Arrays val array = arrayOf(1, 2, 3, 4, 5) val typedArray = intArrayOf(1, 2, 3) // Primitive array val arrayWithNulls = arrayOfNulls(5) // 5. Type Checks and Casts val obj: Any = "I'm a string" // Smart cast after type check if (obj is String) { println(obj.uppercase()) // No explicit cast needed } // Safe cast returns null if not possible val number: Int? = obj as? Int println("Safe cast result: $number") // 6. Basic Operations val a = 10 val b = 3 println("a + b = ${a + b}") println("a - b = ${a - b}") println("a * b = ${a * b}") println("a / b = ${a / b}") // Integer division println("a % b = ${a % b}") // Range operator val range = 1..10 val exclusiveRange = 1 until 10 val descendingRange = 10 downTo 1 val stepRange = 1..10 step 2 // 7. Equality val str1 = "Hello" val str2 = String("Hello".toCharArray()) println("str1 == str2: ${str1 == str2}") // Structural equality println("str1 === str2: ${str1 === str2}") // Referential equality // 8. Basic Control Flow val score = 85 // If as expression val grade = if (score >= 90) { "A" } else if (score >= 80) { "B" } else if (score >= 70) { "C" } else { "F" } println("Score: $score, Grade: $grade") // When expression (powerful switch) val day = 3 val dayName = when (day) { 1 -> "Monday" 2 -> "Tuesday" 3 -> "Wednesday" 4 -> "Thursday" 5 -> "Friday" 6, 7 -> "Weekend" in 8..31 -> "Invalid day" else -> "Unknown" } println("Day $day is $dayName") // Loops for (i in 1..5) { print("$i ") } println() for (i in 1 until 5) { print("$i ") } println() for (i in 5 downTo 1) { print("$i ") } println() // Iterating with index val fruits = listOf("Apple", "Banana", "Cherry") for ((index, fruit) in fruits.withIndex()) { println("Fruit $index: $fruit") } // While loop var count = 5 while (count > 0) { print("$count ") count-- } println() // Do-while loop var input: String do { print("Enter 'quit' to exit: ") input = readLine() ?: "" } while (input != "quit") println("Basics complete!") }

📦 Variables & Types

Variables and Types in Kotlin represent a significant improvement over traditional Java approaches. Kotlin's type system is designed to be both expressive and safe, with features that prevent common programming errors while reducing boilerplate code. The language emphasizes immutability and provides powerful type inference capabilities.

Variable Declarations: val vs var

Kotlin distinguishes between mutable and immutable variables at the declaration level. The `val` keyword creates read-only variables that can be assigned only once (similar to Java's final). The `var` keyword creates mutable variables that can be reassigned. This explicit distinction encourages immutability by default, which leads to safer, more predictable code, especially in concurrent programming scenarios.

Type Inference System

Kotlin's compiler features sophisticated type inference that can determine variable types from context. When you assign a value to a variable, the compiler can usually infer its type, allowing you to omit explicit type declarations. However, you can still specify types explicitly when needed for clarity or when the compiler cannot infer the type. This balance reduces verbosity while maintaining type safety.

Basic Data Types

Kotlin treats all types as objects—there are no primitive types in the traditional sense (though they are optimized at runtime). The basic types include: `Byte`, `Short`, `Int`, `Long` for integers; `Float` and `Double` for floating-point numbers; `Char` for characters; `Boolean` for truth values; and `String` for text. Arrays are represented by the `Array` class, with specialized variants like `IntArray` for performance.

Nullable Types System

One of Kotlin's most important features is its null safety system. By default, types are non-nullable—they cannot hold null values. To allow nullability, you must explicitly add a question mark: `String?`. The compiler tracks nullability and ensures safe handling of nullable types through safe calls (`?.`), the Elvis operator (`?:`), and non-null assertions (`!!`). This system eliminates null pointer exceptions at compile time.

Type Aliases and Smart Casts

Kotlin allows creating aliases for existing types using the `typealias` keyword, improving code readability for complex generic types. The smart cast feature automatically casts variables after type checks, eliminating the need for explicit casting in many cases. After checking `if (obj is String)`, you can use `obj` as a String without casting within that scope.

Properties and Backing Fields

In Kotlin, variables in classes are actually properties with automatically generated getters and setters. You can customize accessors using `get()` and `set()` blocks. Backing fields (accessed via `field`) store the actual value. This unified approach eliminates the Java pattern of private fields with public getters/setters, making code more concise.

Late Initialization and Delegated Properties

For variables that cannot be initialized immediately, Kotlin provides `lateinit` for non-nullable types (with runtime checks) and `by lazy` for lazy initialization. Delegated properties allow custom logic for property access through delegation. The standard library includes delegates for observable properties (`observable`), vetoable changes (`vetoable`), and map storage (`by map`).

// Variables and Types Examples fun main() { println("=== Variables and Types ===") // 1. val vs var val immutableVal = "I cannot be reassigned" // immutableVal = "New value" // Compilation error var mutableVar = "I can be changed" mutableVar = "New value allowed" // 2. Type inference examples val inferredInt = 42 // Int val inferredDouble = 3.14 // Double val inferredString = "Hello" // String val inferredBoolean = true // Boolean val inferredList = listOf(1, 2, 3) // List // 3. Explicit type declarations val explicitLong: Long = 10000000000L val explicitFloat: Float = 3.14f val explicitArray: Array = arrayOf(1, 2, 3) // 4. Nullable types var nullableString: String? = null // Can be null var nonNullableString: String = "Hello" // Cannot be null // nonNullableString = null // Compilation error // Safe calls val length = nullableString?.length // Returns null if nullableString is null println("Length of nullable string: $length") // Elvis operator (default value) val safeLength = nullableString?.length ?: 0 println("Safe length: $safeLength") // Non-null assertion (use with caution!) nullableString = "Now I have a value" val assertedLength = nullableString!!.length println("Asserted length: $assertedLength") // 5. Type checks and smart casts fun processValue(value: Any) { when (value) { is String -> { // Smart cast to String println("String length: ${value.length}") } is Int -> { // Smart cast to Int println("Int value doubled: ${value * 2}") } is List<*> -> { // Smart cast to List println("List size: ${value.size}") } else -> { println("Unknown type") } } } processValue("Kotlin") processValue(42) processValue(listOf(1, 2, 3)) // 6. Type aliases typealias UserId = String typealias UserMap = Map val userId: UserId = "user123" val users: UserMap = mapOf("user123" to "Alice", "user456" to "Bob") // 7. Arrays val intArray = intArrayOf(1, 2, 3, 4, 5) val stringArray = arrayOf("Apple", "Banana", "Cherry") val mixedArray = arrayOf(1, "Two", 3.0, true) // Array operations println("Int array size: ${intArray.size}") println("First element: ${stringArray[0]}") println("Last element: ${stringArray[stringArray.lastIndex]}") // 8. Type conversions val intValue: Int = 42 val longValue: Long = intValue.toLong() // Explicit conversion needed val doubleValue: Double = intValue.toDouble() val stringValue: String = intValue.toString() // 9. Properties with custom accessors class Rectangle(val width: Int, val height: Int) { // Computed property val area: Int get() = width * height // Property with backing field var scale: Double = 1.0 set(value) { if (value > 0) { field = value } else { println("Scale must be positive") } } // Lazy property val diagonal: Double by lazy { Math.sqrt((width * width + height * height).toDouble()) } } val rect = Rectangle(10, 20) println("Rectangle area: ${rect.area}") println("Rectangle diagonal: ${rect.diagonal}") rect.scale = 2.0 println("Scale set to: ${rect.scale}") // 10. Late initialization class Service { lateinit var data: String fun initialize() { data = "Initialized data" } fun process() { // Check if initialized if (::data.isInitialized) { println("Processing: $data") } else { println("Data not initialized") } } } val service = Service() service.process() service.initialize() service.process() // 11. Delegated properties class User { var name: String by Delegates.observable("") { prop, old, new -> println("$old -> $new") } var age: Int by Delegates.vetoable(0) { prop, old, new -> new >= 0 // Only allow positive ages } } val user = User() user.name = "Alice" user.name = "Bob" user.age = 25 user.age = -5 // Won't change (vetoed) println("User age: ${user.age}") // 12. Any, Unit, Nothing val anyValue: Any = "Can be anything" val unitValue: Unit = println("Returns Unit") // Similar to void fun neverReturns(): Nothing { throw RuntimeException("This function never returns normally") } // 13. Type parameters and generics class Box(val value: T) { fun getValue(): T = value } val intBox = Box(42) val stringBox = Box("Hello") println("Box values: ${intBox.getValue()}, ${stringBox.getValue()}") println("\nVariables and Types demonstration complete!") }

🏹 Functions in Kotlin

Functions in Kotlin are first-class citizens with extensive features that go beyond traditional Java methods. They can be declared at the top level, as member functions, as local functions, or as extension functions. Kotlin's function design emphasizes expressiveness, safety, and interoperability while reducing boilerplate code significantly.

Function Declaration Syntax

Kotlin functions are declared with the `fun` keyword followed by the function name, parameters in parentheses, an optional return type, and a body. Parameters use Pascal notation (name: type) and are separated by commas. The return type is specified after the parameters with a colon. If the function returns nothing meaningful, the return type is `Unit`, which can be omitted.

Default and Named Arguments

Kotlin supports default parameter values, allowing functions to be called with fewer arguments than defined. This eliminates the need for overloaded functions in many cases. Named arguments enable calling functions with explicit parameter names, improving readability and allowing arguments to be passed in any order when combined with default values.

Single-Expression Functions

When a function consists of a single expression, Kotlin allows an abbreviated syntax where the return type can be inferred and the curly braces replaced with `=`. This makes simple functions extremely concise. For example, `fun square(x: Int) = x * x` is equivalent to a full function declaration but much cleaner.

Extension Functions

One of Kotlin's most powerful features is extension functions, which allow adding new functionality to existing classes without modifying their source code or using inheritance. Extension functions are resolved statically (they don't actually modify the class) but provide a natural, fluent API. They're particularly useful for enhancing library classes or creating DSLs.

Infix Functions

Functions marked with the `infix` keyword can be called using infix notation (without dots and parentheses for single parameters). This enables creating domain-specific languages and operators that read naturally. Infix functions must be member functions or extension functions with a single parameter.

Higher-Order Functions and Lambdas

Kotlin treats functions as values that can be passed as arguments, returned from other functions, or assigned to variables. Higher-order functions take functions as parameters or return functions. Lambda expressions provide a concise way to define anonymous functions, with several syntactic sugars like trailing lambdas and implicit `it` parameter for single-parameter lambdas.

Local Functions and Tail Recursion

Kotlin supports local functions—functions defined inside other functions. These can access variables from the enclosing scope, reducing duplication and improving encapsulation. Kotlin also supports tail-recursive functions with the `tailrec` modifier, which the compiler optimizes to iterative loops, preventing stack overflow for recursive algorithms.

// Functions in Kotlin Examples fun main() { println("=== Functions in Kotlin ===") // 1. Basic function fun greet(name: String): String { return "Hello, $name!" } println(greet("Kotlin Developer")) // 2. Single-expression function fun square(x: Int) = x * x fun isEven(n: Int) = n % 2 == 0 println("Square of 5: ${square(5)}") println("Is 6 even? ${isEven(6)}") // 3. Default arguments fun createMessage( text: String, prefix: String = "Info", suffix: String = "End of message", times: Int = 1 ): String { return "$prefix: ${text.repeat(times)} ($suffix)" } println(createMessage("Hello")) // Uses all defaults println(createMessage("Hello", "Warning")) // Custom prefix println(createMessage("Hello", times = 3)) // Named argument // 4. Named arguments fun formatUser( firstName: String, lastName: String, age: Int, email: String = "", phone: String = "" ) { println("User: $firstName $lastName, Age: $age") if (email.isNotEmpty()) println("Email: $email") if (phone.isNotEmpty()) println("Phone: $phone") } formatUser("John", "Doe", 30) formatUser( firstName = "Jane", lastName = "Smith", age = 25, email = "jane@example.com" ) // Can change order with named arguments formatUser( age = 35, lastName = "Brown", firstName = "Bob", phone = "555-1234" ) // 5. Extension functions fun String.addExclamation() = "$this!" fun Int.isPositive() = this > 0 fun List.product(): Int = this.fold(1) { acc, i -> acc * i } println("Hello".addExclamation()) println("Is 5 positive? ${5.isPositive()}") println("Product of [1, 2, 3, 4]: ${listOf(1, 2, 3, 4).product()}") // 6. Infix functions infix fun Int.times(str: String) = str.repeat(this) infix fun String.onto(other: String) = Pair(this, other) println(3 times "Hi ") // Using infix notation println("Kotlin" onto "Java") class Person(val name: String) { infix fun likes(thing: String) = "$name likes $thing" } val alice = Person("Alice") println(alice likes "programming") // 7. Operator overloading data class Point(val x: Int, val y: Int) { operator fun plus(other: Point) = Point(x + other.x, y + other.y) operator fun minus(other: Point) = Point(x - other.x, y - other.y) operator fun times(factor: Int) = Point(x * factor, y * factor) operator fun component1() = x // For destructuring operator fun component2() = y } val p1 = Point(10, 20) val p2 = Point(5, 5) println("p1 + p2 = ${p1 + p2}") println("p1 * 3 = ${p1 * 3}") // Destructuring using component operators val (x, y) = p1 println("Destructured: x=$x, y=$y") // 8. Local functions fun processNumbers(vararg numbers: Int) { // Local function fun validate(n: Int): Boolean { return n in 1..100 } val validNumbers = numbers.filter { validate(it) } println("Valid numbers: $validNumbers") } processNumbers(10, 150, 30, -5, 75) // 9. Tail recursive functions tailrec fun factorial(n: Int, accumulator: Int = 1): Int { return if (n <= 1) accumulator else factorial(n - 1, n * accumulator) } println("Factorial of 5: ${factorial(5)}") // 10. Higher-order functions fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int { return operation(x, y) } val sum = calculate(10, 5) { a, b -> a + b } val product = calculate(10, 5) { a, b -> a * b } println("Sum: $sum, Product: $product") // Function returning function fun multiplier(factor: Int): (Int) -> Int { return { number -> number * factor } } val double = multiplier(2) val triple = multiplier(3) println("Double 5: ${double(5)}") println("Triple 5: ${triple(5)}") // 11. Lambda expressions val numbers = listOf(1, 2, 3, 4, 5) // Traditional lambda val squares = numbers.map { n -> n * n } // Using it for single parameter val evens = numbers.filter { it % 2 == 0 } // Multi-line lambda val processed = numbers.map { println("Processing $it") it * 2 + 1 } println("Squares: $squares") println("Evens: $evens") println("Processed: $processed") // 12. Function references fun isPrime(n: Int): Boolean { if (n <= 1) return false for (i in 2..n/2) { if (n % i == 0) return false } return true } val primes = (1..20).filter(::isPrime) println("Primes 1-20: $primes") // 13. Inline functions inline fun measureTime(block: () -> Unit): Long { val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start } val time = measureTime { // This code block is inlined at compile time var sum = 0 for (i in 1..1000000) { sum += i } } println("Computation took ${time}ms") // 14. Varargs fun sumAll(vararg numbers: Int): Int { return numbers.sum() } println("Sum: ${sumAll(1, 2, 3, 4, 5)}") // Spread operator val arr = intArrayOf(10, 20, 30) println("Sum from array: ${sumAll(*arr)}") // 15. Generic functions fun printList(list: List) { list.forEach { println(it) } } fun > maxOf(a: T, b: T): T { return if (a > b) a else b } println("Max of 10 and 20: ${maxOf(10, 20)}") println("Max of 'a' and 'b': ${maxOf("a", "b")}") println("\nFunctions demonstration complete!") }

🎯 Control Flow

Control Flow in Kotlin represents a significant evolution from traditional C-style languages, with expressions replacing statements in many cases. Kotlin's control flow structures are designed to be both powerful and safe, eliminating common sources of errors while providing concise syntax. The language embraces expression-oriented programming where control structures return values.

If as an Expression

In Kotlin, `if` can be used as an expression that returns a value. This eliminates the need for the ternary operator (`?:`) found in other languages. The last expression in each branch becomes the return value. When used as an expression, the `else` branch is mandatory. This design makes code more readable and reduces the chance of missing else cases that can cause null pointer exceptions.

When Expression: Supercharged Switch

The `when` expression is Kotlin's replacement for switch statements, but far more powerful. It can match against constants, ranges, types, or even arbitrary conditions. `when` can be used with or without an argument, and like `if`, it returns a value when used as an expression. The compiler checks for exhaustiveness when used with sealed classes or enums, ensuring all cases are handled.

For Loops and Iteration

Kotlin's `for` loop iterates through anything that provides an iterator, including ranges, arrays, collections, and custom types. The syntax is clean: `for (item in collection)`. Ranges are created with `..` (inclusive) or `until` (exclusive). You can iterate with indices using `withIndex()` or over maps with destructuring. The loop supports `break` and `continue` with labels for nested loops.

While and Do-While Loops

Kotlin supports traditional `while` and `do-while` loops with the same semantics as other languages. The `while` loop checks the condition before each iteration, while `do-while` checks after, guaranteeing at least one execution. These loops are statements (don't return values) and are useful when the number of iterations isn't known in advance.

Returns, Jumps, and Labels

Kotlin has three jump expressions: `return` (returns from nearest enclosing function), `break` (terminates nearest enclosing loop), and `continue` (proceeds to next iteration). All can be used with labels to control which enclosing function or loop is affected. Labels are particularly useful in nested loops or lambda expressions where you need to break from an outer scope.

Exception Handling

Kotlin's exception handling uses `try`, `catch`, and `finally` blocks, but unlike Java, exceptions are unchecked (don't need to be declared). The `try` block can be an expression, returning either the try body value or the catch body value. Kotlin also provides the `throw` expression, which has type `Nothing` (a special type indicating the function never returns normally).

Scope Functions for Control

While not traditional control flow, Kotlin's scope functions (`let`, `run`, `with`, `apply`, `also`) provide alternative ways to control execution context. They're particularly useful for null checks, initialization, and creating fluent APIs. Each has different semantics for the receiver (`this` vs `it`) and return value (context object vs lambda result), making them suitable for different control patterns.

// Control Flow Examples fun main() { println("=== Control Flow in Kotlin ===") // 1. If expression (not statement) val a = 10 val b = 20 // Traditional if-else val max1 = if (a > b) a else b println("Max of $a and $b is $max1") // Multi-line if expression (last expression is returned) val description = if (a > b) { println("a is greater") "a is greater" } else if (a < b) { println("b is greater") "b is greater" } else { println("a and b are equal") "a and b are equal" } println("Description: $description") // 2. When expression (replaces switch) val day = 3 // Basic when val dayName = when (day) { 1 -> "Monday" 2 -> "Tuesday" 3 -> "Wednesday" 4 -> "Thursday" 5 -> "Friday" 6, 7 -> "Weekend" else -> "Invalid day" } println("Day $day is $dayName") // When with ranges val score = 85 val grade = when (score) { in 90..100 -> "A" in 80..89 -> "B" in 70..79 -> "C" in 60..69 -> "D" else -> "F" } println("Score $score gets grade $grade") // When with type checks and smart casts fun describe(obj: Any): String = when (obj) { is String -> "String with length ${obj.length}" is Int -> "Integer: $obj" is List<*> -> "List with ${obj.size} elements" else -> "Unknown type" } println(describe("Hello")) println(describe(42)) println(describe(listOf(1, 2, 3))) // When without argument (replaces if-else chains) val hour = 14 val timeOfDay = when { hour < 12 -> "Morning" hour < 17 -> "Afternoon" hour < 21 -> "Evening" else -> "Night" } println("At $hour:00 it's $timeOfDay") // 3. For loops // Range iteration println("Counting up:") for (i in 1..5) { print("$i ") } println() println("Counting down:") for (i in 5 downTo 1) { print("$i ") } println() println("With step:") for (i in 1..10 step 2) { print("$i ") } println() println("Exclusive range:") for (i in 1 until 5) { print("$i ") } println() // Collection iteration val fruits = listOf("Apple", "Banana", "Cherry", "Date") println("Fruits:") for (fruit in fruits) { print("$fruit ") } println() // Iteration with index println("Fruits with index:") for ((index, fruit) in fruits.withIndex()) { println(" $index: $fruit") } // Iteration with indices property println("Using indices:") for (i in fruits.indices) { println(" $i: ${fruits[i]}") } // Map iteration val map = mapOf("A" to 1, "B" to 2, "C" to 3) println("Map entries:") for ((key, value) in map) { println(" $key -> $value") } // String iteration val text = "Kotlin" println("Characters in '$text':") for (ch in text) { print("$ch ") } println() // 4. While and do-while loops println("While loop:") var count = 5 while (count > 0) { print("$count ") count-- } println() println("Do-while loop (always executes at least once):") var input: String do { print("Enter 'exit' to quit: ") input = readLine() ?: "" println("You entered: $input") } while (input != "exit") // 5. Break and continue println("Break example:") for (i in 1..10) { if (i == 5) { println("Breaking at 5") break } print("$i ") } println() println("Continue example:") for (i in 1..10) { if (i % 2 == 0) { continue // Skip even numbers } print("$i ") } println() // 6. Labeled breaks and continues println("Labeled break (outer loop):") outer@ for (i in 1..3) { inner@ for (j in 1..3) { if (i == 2 && j == 2) { break@outer // Break outer loop } println("i=$i, j=$j") } } println("Labeled continue:") outer@ for (i in 1..3) { for (j in 1..3) { if (j == 2) { continue@outer // Continue outer loop } println("i=$i, j=$j") } } // 7. Exception handling println("\nException handling:") // Try as expression val numberStr = "123" val parsedNumber = try { numberStr.toInt() } catch (e: NumberFormatException) { println("Could not parse '$numberStr' as number") null } println("Parsed number: $parsedNumber") // Multiple catch blocks fun riskyOperation(value: String): Int { return try { val num = value.toInt() if (num < 0) throw IllegalArgumentException("Negative number") 100 / num // Could throw ArithmeticException } catch (e: NumberFormatException) { println("Invalid number format: ${e.message}") 0 } catch (e: ArithmeticException) { println("Arithmetic error: ${e.message}") -1 } catch (e: IllegalArgumentException) { println("Illegal argument: ${e.message}") -2 } finally { println("Finally block always executes") } } println("Result 1: ${riskyOperation("10")}") println("Result 2: ${riskyOperation("0")}") println("Result 3: ${riskyOperation("abc")}") println("Result 4: ${riskyOperation("-5")}") // Throw expression fun requirePositive(value: Int): Int { return if (value > 0) { value } else { throw IllegalArgumentException("Value must be positive: $value") } } try { println("Positive check 1: ${requirePositive(10)}") println("Positive check 2: ${requirePositive(-5)}") // Will throw } catch (e: IllegalArgumentException) { println("Caught: ${e.message}") } // 8. Scope functions for control flow println("\nScope functions for control flow:") val nullableString: String? = "Hello" // let for null checks and transformations nullableString?.let { println("String is not null: $it") println("Length: ${it.length}") } // run for object configuration and computing result val result = "Hello".run { println("Processing: $this") length * 2 } println("Run result: $result") // with for calling multiple methods on same object val list = mutableListOf() with(list) { add("First") add("Second") add("Third") println("List has ${size} items: $this") } // apply for object initialization val person = Person("").apply { name = "Alice" age = 30 println("Initialized: $name, $age") } // also for side effects val numbersList = mutableListOf(1, 2, 3) numbersList.also { println("Before adding: $it") it.add(4) println("After adding: $it") } // 9. TakeIf and takeUnless val inputValue = "42" val parsed = inputValue.takeIf { it.all { char -> char.isDigit() } }?.toInt() println("Parsed if all digits: $parsed") val filtered = inputValue.takeUnless { it.isEmpty() } println("Take unless empty: $filtered") println("\nControl flow demonstration complete!") } // Helper class class Person(var name: String) { var age: Int = 0 }

🛡️ Null Safety

Null Safety is one of Kotlin's most celebrated features, designed to eliminate the billion-dollar mistake of null pointer exceptions. Kotlin's type system distinguishes between references that can hold null (nullable references) and those that cannot (non-nullable references). This distinction is made at compile time, allowing the compiler to detect potential null pointer exceptions before the code even runs.

Nullable vs Non-Nullable Types

In Kotlin, types are non-nullable by default. To declare a variable that can hold null, you must explicitly add a question mark after the type: `String?`. The compiler tracks these nullability annotations throughout your code. This explicit nullability forces developers to think about null cases upfront and handle them appropriately, rather than discovering them at runtime.

Safe Call Operator (?.)

The safe call operator `?.` allows you to safely access properties or call methods on nullable objects. If the object is null, the expression returns null instead of throwing a NullPointerException. Safe calls can be chained: `user?.address?.city?.uppercase()`. This eliminates the need for nested null checks and makes code more readable while maintaining safety.

Elvis Operator (?:)

The Elvis operator `?:` provides a default value when the expression on its left is null. It's named for its resemblance to Elvis Presley's hairstyle. The operator evaluates the left expression; if it's not null, that's the result. If it's null, it evaluates and returns the right expression. This is cleaner than using if-else checks for null values.

Non-Null Assertion (!!)

The non-null assertion operator `!!` converts any value to a non-null type and throws a NullPointerException if the value is null. This operator should be used sparingly, only when you're absolutely certain a value cannot be null at that point. Overuse of `!!` defeats Kotlin's null safety guarantees and can lead to runtime crashes.

Safe Casts (as?)

The safe cast operator `as?` attempts to cast a value to a specified type and returns null if the cast is not possible. This is safer than the regular cast operator `as`, which throws a ClassCastException on failure. Safe casts are particularly useful when dealing with types from Java interop or when the type is not guaranteed at compile time.

Platform Types and Java Interoperability

When calling Java code from Kotlin, the nullability of return types is unknown (platform types). Kotlin represents these as types with an exclamation mark: `String!`. You can treat them as nullable or non-nullable, but the compiler can't guarantee safety. Annotations like `@Nullable` and `@NotNull` help Kotlin understand Java code's nullability contracts.

Collection Nullability

Kotlin's collections also support null safety. You can have collections of nullable types (`List`) or nullable collections (`List?`), or both. The standard library provides functions like `filterNotNull()` to safely handle collections containing nulls. This comprehensive approach ensures null safety throughout your data structures.

// Null Safety Examples fun main() { println("=== Null Safety in Kotlin ===") // 1. Nullable vs non-nullable types var nonNullable: String = "Hello" // Cannot be null // nonNullable = null // Compilation error var nullable: String? = "Hello" // Can be null nullable = null // This is allowed // 2. Safe call operator (?.) val length1 = nullable?.length // Returns null if nullable is null println("Length with safe call: $length1") // Chain safe calls class Address(val city: String?) class User(val name: String, val address: Address?) val user1: User? = User("Alice", Address("New York")) val user2: User? = User("Bob", null) val user3: User? = null val city1 = user1?.address?.city // "New York" val city2 = user2?.address?.city // null val city3 = user3?.address?.city // null println("Cities: $city1, $city2, $city3") // Safe call with method calls val uppercaseCity = user1?.address?.city?.uppercase() println("Uppercase city: $uppercaseCity") // 3. Elvis operator (?:) val name1: String? = "Alice" val name2: String? = null val displayName1 = name1 ?: "Anonymous" val displayName2 = name2 ?: "Anonymous" println("Display names: $displayName1, $displayName2") // Elvis with throw fun requireNotNull(value: String?): String { return value ?: throw IllegalArgumentException("Value cannot be null") } try { println("Required: ${requireNotNull("Valid")}") println("Required: ${requireNotNull(null)}") // Throws } catch (e: IllegalArgumentException) { println("Caught: ${e.message}") } // Elvis with return fun processUser(user: User?) { val userName = user?.name ?: return // Return early if null println("Processing user: $userName") } processUser(User("Charlie", null)) processUser(null) // 4. Non-null assertion (!!) - Use with caution! val definitelyNotNull: String? = "I'm definitely not null" val forcedLength = definitelyNotNull!!.length // We're sure it's not null println("Forced length: $forcedLength") // Dangerous example (will crash at runtime) val risky: String? = null // val crash = risky!!.length // Throws NullPointerException // 5. Safe casts (as?) val obj: Any = "I'm a string" val asString: String? = obj as? String // Safe cast, returns String val asInt: Int? = obj as? Int // Safe cast, returns null println("Safe cast to String: $asString") println("Safe cast to Int: $asInt") // 6. let, also, run, with, apply with null safety val nullableValue: String? = "Hello" // let executes block only if not null nullableValue?.let { println("Value is not null: $it") println("Length: ${it.length}") } // also for side effects nullableValue?.also { println("Also: $it") } // 7. Collections with nulls val listWithNulls: List = listOf(1, 2, null, 4, null, 6) println("List with nulls: $listWithNulls") // Filter out nulls val withoutNulls: List = listWithNulls.filterNotNull() println("Without nulls: $withoutNulls") // Map with null handling val mapped = listWithNulls.map { it?.times(2) } println("Mapped (keeping nulls): $mapped") val mappedNotNull = listWithNulls.mapNotNull { it?.times(2) } println("Mapped (filtering nulls): $mappedNotNull") // 8. Platform types from Java // When calling Java code, Kotlin doesn't know nullability // Java: public String getUserName() { ... } // Kotlin sees this as: fun getUserName(): String! // You can assign to nullable or non-nullable val fromJava1: String = JavaInterop.getUserName() // Assume not null val fromJava2: String? = JavaInterop.getUserName() // Handle possible null println("From Java (non-null): $fromJava1") println("From Java (nullable): $fromJava2") // 9. Late initialization (lateinit) class Service { lateinit var data: String // Will be initialized later fun initialize() { data = "Initialized" } fun process() { // Check if initialized if (::data.isInitialized) { println("Processing: $data") } else { println("Data not initialized yet") } } } val service = Service() service.process() service.initialize() service.process() // 10. Delegates for null safety import kotlin.properties.Delegates class UserProfile { // Not null, must be initialized in constructor or init block var username: String by Delegates.notNull() // Observable property var email: String by Delegates.observable("") { prop, old, new -> println("Email changed: $old -> $new") } // Vetoable - can reject changes var age: Int by Delegates.vetoable(0) { _, old, new -> if (new >= 0) { true // Accept change } else { println("Age cannot be negative: $new") false // Reject change } } } val profile = UserProfile() profile.username = "alice123" // Must be set before use profile.email = "alice@example.com" profile.email = "alice.new@example.com" profile.age = 25 profile.age = -5 // Will be rejected println("Profile age: ${profile.age}") // 11. Type parameter nullability fun printHashCode(t: T) { // T is non-nullable println("Hash code: ${t.hashCode()}") } // printHashCode(null) // Compilation error printHashCode("Hello") fun printHashCodeNullable(t: T) { // T can be nullable println("Hash code nullable: ${t?.hashCode()}") } printHashCodeNullable("Hello") printHashCodeNullable(null) // 12. Null safety in when expressions fun describeValue(value: Any?): String { return when (value) { null -> "It's null" is String -> "String of length ${value.length}" is Int -> "Integer: $value" else -> "Unknown type" } } println(describeValue(null)) println(describeValue("Kotlin")) println(describeValue(42)) // 13. TakeIf and takeUnless val input = "123" val number = input.takeIf { it.all { char -> char.isDigit() } }?.toInt() println("Number if all digits: $number") val notEmpty = input.takeUnless { it.isEmpty() } println("Take unless empty: $notEmpty") // 14. Custom null safety extensions fun String?.orEmpty(): String = this ?: "" fun String?.toIntOrZero(): Int = this?.toIntOrNull() ?: 0 val maybeString: String? = null println("Or empty: '${maybeString.orEmpty()}'") println("To int or zero: ${maybeString.toIntOrZero()}") val maybeNumber: String? = "42" println("To int or zero (valid): ${maybeNumber.toIntOrZero()}") println("\nNull safety demonstration complete!") } // Simulated Java interop object JavaInterop { fun getUserName(): String { return "JavaUser" } }

🏗️ OOP in Kotlin

Object-Oriented Programming (OOP) in Kotlin builds upon Java's OOP foundation while fixing its pain points and introducing modern features. Kotlin supports all four OOP pillars—encapsulation, inheritance, polymorphism, and abstraction—but implements them more cleanly and concisely. The language eliminates boilerplate code while enhancing expressiveness and safety.

Classes and Constructors

Kotlin classes are declared with the `class` keyword. The primary constructor is part of the class header and can declare properties directly. Secondary constructors use the `constructor` keyword and must delegate to the primary constructor. Properties declared in the primary constructor can be `val` (read-only) or `var` (mutable). Kotlin automatically generates getters and setters for properties.

Properties and Fields

In Kotlin, fields don't exist as a first-class concept—instead, we have properties with automatically generated backing fields when needed. You can customize property accessors with `get()` and `set()` blocks. The `field` identifier refers to the backing field in custom accessors. This unified approach eliminates the Java pattern of private fields with public getters/setters.

Inheritance and Open Classes

By default, Kotlin classes are `final`—they cannot be inherited. To allow inheritance, mark the class with the `open` keyword. Methods must also be marked `open` to be overridable. The `override` modifier is required when overriding methods (unlike Java's optional `@Override` annotation). This explicit design prevents accidental inheritance and fragile base class problems.

Interfaces with Default Implementations

Kotlin interfaces can contain method implementations (similar to Java 8 default methods) and properties. Interfaces support multiple inheritance, and classes can implement multiple interfaces. Interface properties can be abstract or provide accessor implementations. Kotlin's interfaces are more powerful than Java's, allowing rich API design without abstract classes in many cases.

Data Classes for Value Objects

Data classes (`data class`) automatically generate `equals()`, `hashCode()`, `toString()`, `copy()`, and component functions (for destructuring). They're ideal for value objects that primarily hold data. Properties declared in the primary constructor are used in the generated functions. Data classes reduce boilerplate dramatically while providing value semantics.

Sealed Classes for Restricted Hierarchies

Sealed classes restrict which classes can inherit from them. All subclasses must be declared in the same file. This makes sealed classes perfect for representing restricted class hierarchies, like algebraic data types. The compiler knows all possible subclasses, enabling exhaustive checks in `when` expressions without needing an `else` branch.

Objects and Companion Objects

Kotlin uses `object` declarations for singletons—classes that have only one instance. Companion objects are objects declared inside a class with the `companion` keyword, serving as static members in Java. They can have names, implement interfaces, and extend classes. Object expressions create anonymous objects (similar to Java anonymous classes but more powerful).

// OOP in Kotlin Examples fun main() { println("=== Object-Oriented Programming in Kotlin ===") // 1. Basic class with primary constructor class Person(val name: String, var age: Int) { // Property with custom getter val isAdult: Boolean get() = age >= 18 // Method fun greet() { println("Hello, I'm $name and I'm $age years old") } } val alice = Person("Alice", 25) alice.greet() println("Is Alice adult? ${alice.isAdult}") // 2. Properties with backing fields class Rectangle(width: Int, height: Int) { var width = width set(value) { if (value > 0) { field = value } else { println("Width must be positive") } } var height = height set(value) { if (value > 0) { field = value } } // Computed property (no backing field) val area: Int get() = width * height } val rect = Rectangle(10, 20) println("Rectangle area: ${rect.area}") rect.width = -5 // Will print warning println("Width after invalid set: ${rect.width}") // 3. Inheritance open class Animal(val name: String) { // open allows inheritance open fun makeSound() { // open allows overriding println("$name makes a sound") } fun eat() { println("$name is eating") } } class Dog(name: String, val breed: String) : Animal(name) { override fun makeSound() { println("$name (a $breed) barks!") } fun fetch() { println("$name is fetching") } } val dog = Dog("Rex", "Golden Retriever") dog.makeSound() dog.eat() dog.fetch() // 4. Abstract classes abstract class Shape { abstract fun area(): Double abstract fun perimeter(): Double fun description() { println("Area: ${area()}, Perimeter: ${perimeter()}") } } class Circle(val radius: Double) : Shape() { override fun area() = Math.PI * radius * radius override fun perimeter() = 2 * Math.PI * radius } val circle = Circle(5.0) circle.description() // 5. Interfaces interface Drawable { fun draw() val color: String // Abstract property fun description() { // Default implementation println("Drawable with color $color") } } interface Clickable { fun click() fun show() = println("Showing clickable element") // Default implementation } class Button(override val color: String) : Drawable, Clickable { override fun draw() { println("Drawing button with color $color") } override fun click() { println("Button clicked!") } // Can override default implementation override fun show() { super.show() // Call super from specific interface println("Button specific show") } } val button = Button("Blue") button.draw() button.click() button.description() button.show() // 6. Data classes data class User(val id: Int, val username: String, val email: String) val user1 = User(1, "alice", "alice@example.com") val user2 = User(1, "alice", "alice@example.com") val user3 = User(2, "bob", "bob@example.com") println("user1: $user1") println("user1 == user2: ${user1 == user2}") // true - structural equality println("user1 === user2: ${user1 === user2}") // false - different references // Copy with modification val user1Updated = user1.copy(email = "alice.new@example.com") println("Updated user: $user1Updated") // Destructuring val (id, username, email) = user1 println("Destructured: id=$id, username=$username, email=$email") // Component functions (auto-generated) println("Components: ${user1.component1()}, ${user1.component2()}, ${user1.component3()}") // 7. Sealed classes (restricted hierarchies) sealed class Result { data class Success(val data: String) : Result() data class Error(val message: String, val code: Int) : Result() object Loading : Result() } fun handleResult(result: Result) { // Exhaustive when (no else needed - compiler knows all cases) when (result) { is Result.Success -> println("Success: ${result.data}") is Result.Error -> println("Error ${result.code}: ${result.message}") Result.Loading -> println("Loading...") } } handleResult(Result.Success("Data loaded")) handleResult(Result.Error("Not found", 404)) handleResult(Result.Loading) // 8. Objects (singletons) object Logger { private var level = "INFO" fun log(message: String) { println("[$level] $message") } fun setLevel(newLevel: String) { level = newLevel } } Logger.log("Application started") Logger.setLevel("DEBUG") Logger.log("Debug message") // 9. Companion objects (static members) class DatabaseClient private constructor(val url: String) { companion object Factory { private val instances = mutableMapOf() fun getInstance(url: String): DatabaseClient { return instances.getOrPut(url) { DatabaseClient(url) } } const val DEFAULT_URL = "localhost:5432" } fun connect() { println("Connected to $url") } } val db1 = DatabaseClient.getInstance("localhost:5432") val db2 = DatabaseClient.getInstance("localhost:5432") // Same instance val db3 = DatabaseClient.getInstance("remote:5432") // Different instance println("db1 === db2: ${db1 === db2}") // true println("db1 === db3: ${db1 === db3}") // false db1.connect() println("Default URL: ${DatabaseClient.DEFAULT_URL}") // 10. Nested and inner classes class Outer { private val outerValue = "Outer" // Nested class (no access to outer instance) class Nested { fun getValue() = "From nested" } // Inner class (has access to outer instance) inner class Inner { fun getValue() = "From inner accessing $outerValue" } } val nested = Outer.Nested() println("Nested: ${nested.getValue()}") val outer = Outer() val inner = outer.Inner() println("Inner: ${inner.getValue()}") // 11. Object expressions (anonymous objects) interface EventListener { fun onEvent(event: String) } val listener = object : EventListener { override fun onEvent(event: String) { println("Event received: $event") } // Can have additional members val creationTime = System.currentTimeMillis() } listener.onEvent("click") println("Listener created at: ${listener.creationTime}") // 12. Visibility modifiers class VisibilityExample { public val publicProperty = "Everyone can see me" internal val internalProperty = "Same module only" protected val protectedProperty = "Subclasses can see me" private val privateProperty = "Only this class can see me" fun test() { println(publicProperty) println(internalProperty) println(protectedProperty) println(privateProperty) } } class Subclass : VisibilityExample() { fun testSubclass() { println(publicProperty) println(internalProperty) println(protectedProperty) // OK - protected is visible // println(privateProperty) // Error - private not visible } } // 13. Delegation interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { println("BaseImpl: $x") } } // Delegation using "by" keyword class Derived(b: Base) : Base by b { // Can override if needed override fun print() { println("Derived before") // b.print() // Called automatically by delegation println("Derived after") } } val base = BaseImpl(10) Derived(base).print() // 14. Property delegation class DelegateExample { var delegatedProperty: String by Delegate() } class Delegate { private var backingField = "Default" operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): String { println("Getting ${property.name}") return backingField } operator fun setValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>, value: String) { println("Setting ${property.name} to $value") backingField = value } } val example = DelegateExample() println("Initial value: ${example.delegatedProperty}") example.delegatedProperty = "New Value" println("Updated value: ${example.delegatedProperty}") // 15. Enum classes enum class Color(val rgb: Int) { RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF), YELLOW(0xFFFF00); fun containsRed() = (rgb and 0xFF0000) != 0 } println("Colors:") for (color in Color.values()) { println(" $color (${color.rgb.toString(16)}) contains red? ${color.containsRed()}") } val selected = Color.valueOf("RED") println("Selected color: $selected, ordinal: ${selected.ordinal}") println("\nOOP demonstration complete!") }

🏛️ Classes & Objects

Classes and Objects in Kotlin represent the core building blocks of object-oriented programming, with significant improvements over traditional Java approaches. Kotlin's class system is designed to be concise, safe, and expressive while eliminating common sources of boilerplate code. The language provides several specialized class types (data, sealed, enum, inline) for specific use cases.

Class Declarations and Constructors

Kotlin classes are declared with the `class` keyword. The primary constructor is part of the class header and can have parameters that automatically become properties if declared with `val` or `var`. Secondary constructors use the `constructor` keyword and must delegate to the primary constructor using `this()`. Initializer blocks (`init`) run during object creation and can contain validation logic or complex initialization.

Properties and Custom Accessors

Properties in Kotlin combine fields and accessors into a single concept. By default, properties have automatically generated getters and setters. You can customize accessors using `get()` and `set()` blocks. The `field` identifier refers to the backing field in custom accessors. Computed properties have no backing field and calculate their value on each access.

Visibility Modifiers

Kotlin provides four visibility modifiers: `public` (default), `internal` (visible within the same module), `protected` (visible in subclasses), and `private` (visible within the containing scope). Unlike Java, Kotlin doesn't have package-private visibility. The modifiers can be applied to classes, functions, properties, and constructors to control encapsulation.

Companion Objects for Static Members

Kotlin doesn't have static members. Instead, it uses companion objects—objects declared inside a class with the `companion` keyword. Companion object members can be called using the class name as a qualifier. Companion objects can have names, implement interfaces, and be referenced as objects. They're more flexible than Java's static members while serving the same purpose.

Object Declarations for Singletons

Object declarations create singletons—classes with a single instance. The object is created lazily when accessed for the first time. Objects can extend classes and implement interfaces. They're useful for utility classes, factory patterns, or when you need a single instance throughout the application. Object expressions create anonymous objects on the fly.

Nested and Inner Classes

Kotlin distinguishes between nested classes (declared with `class`) and inner classes (declared with `inner class`). Nested classes don't have access to the outer class instance, similar to Java's static nested classes. Inner classes have access to the outer class instance and can reference it using `this@Outer`. This explicit distinction prevents confusion.

Inline Classes for Type Safety

Inline classes (value classes) wrap a single value while having no runtime overhead—they're represented as the underlying value at runtime. They provide type safety without performance cost. Inline classes are declared with `value class` and must have a single `val` property in the primary constructor. They're ideal for creating type-safe abstractions over primitive types.

// Classes and Objects Examples fun main() { println("=== Classes and Objects in Kotlin ===") // 1. Basic class with primary constructor class Person constructor(firstName: String) { // constructor keyword optional val name: String init { // Initializer block name = firstName.capitalize() println("Person initialized with name: $name") } } val person1 = Person("alice") println("Person name: ${person1.name}") // 2. Concise primary constructor with properties class User(val username: String, var age: Int) { // Properties automatically created from constructor parameters fun describe() { println("User: $username, Age: $age") } } val user = User("john_doe", 30) user.describe() user.age = 31 // Setter available because age is var user.describe() // 3. Secondary constructors class Car(val make: String, val model: String) { var year: Int = 2023 // Secondary constructor must delegate to primary constructor(make: String, model: String, year: Int) : this(make, model) { this.year = year println("Car created with year: $year") } // Another secondary constructor constructor(make: String) : this(make, "Unknown") { println("Car created with unknown model") } fun info() { println("$year $make $model") } } val car1 = Car("Toyota", "Camry") val car2 = Car("Honda", "Accord", 2020) val car3 = Car("Ford") car1.info() car2.info() car3.info() // 4. Properties with custom accessors class Rectangle(width: Int, height: Int) { var width = width set(value) { if (value > 0) { field = value // field refers to backing field } else { println("Width must be positive") } } var height = height get() { println("Getting height: $field") return field } set(value) { println("Setting height from $field to $value") field = value } // Computed property (no backing field) val area: Int get() = width * height // Property with private setter var scale: Double = 1.0 private set fun doubleScale() { scale *= 2 } } val rect = Rectangle(10, 20) println("Rectangle area: ${rect.area}") rect.width = 15 rect.height = 25 println("New area: ${rect.area}") rect.doubleScale() println("Scale: ${rect.scale}") // rect.scale = 3.0 // Error - setter is private // 5. Lazy properties class HeavyComputation { val expensiveResult: String by lazy { println("Computing expensive result...") // Simulate expensive computation Thread.sleep(1000) "Computed Result" } } val computation = HeavyComputation() println("Before accessing lazy property") println("Lazy property: ${computation.expensiveResult}") println("Lazy property (cached): ${computation.expensiveResult}") // 6. Late initialization class DatabaseService { lateinit var connection: String fun connect() { connection = "Connected to database" } fun query() { if (::connection.isInitialized) { println("Querying using: $connection") } else { println("Not connected yet") } } } val service = DatabaseService() service.query() service.connect() service.query() // 7. Companion objects (static members) class MathUtils { companion object { const val PI = 3.14159 fun circleArea(radius: Double): Double { return PI * radius * radius } // Factory method fun create(): MathUtils = MathUtils() } fun instanceMethod() { println("I'm an instance method") } } // Access companion members using class name println("PI: ${MathUtils.PI}") println("Circle area: ${MathUtils.circleArea(5.0)}") val utils = MathUtils.create() utils.instanceMethod() // Named companion object class Logger { companion object Factory { fun createLogger(): Logger = Logger() } } val logger = Logger.Factory.createLogger() // 8. Object declarations (singletons) object ApplicationConfig { private const val VERSION = "1.0.0" var debugMode = false fun printConfig() { println("Version: $VERSION, Debug: $debugMode") } } ApplicationConfig.debugMode = true ApplicationConfig.printConfig() // 9. Object expressions (anonymous objects) interface ClickListener { fun onClick() } val buttonListener = object : ClickListener { override fun onClick() { println("Button clicked!") } // Can have additional properties/methods val clickCount = 0 } buttonListener.onClick() println("Click count: ${buttonListener.clickCount}") // Object expression without supertype val adHoc = object { val x = 10 val y = 20 fun sum() = x + y } println("Ad hoc object sum: ${adHoc.sum()}") // 10. Nested and inner classes class OuterClass(val outerValue: String) { // Nested class (no access to outer instance) class NestedClass { fun nestedMethod() = "From nested class" } // Inner class (has access to outer instance) inner class InnerClass { fun innerMethod() = "From inner class accessing $outerValue" fun getOuterReference(): OuterClass = this@OuterClass } } val outer = OuterClass("Outer Value") val nested = OuterClass.NestedClass() val inner = outer.InnerClass() println("Nested: ${nested.nestedMethod()}") println("Inner: ${inner.innerMethod()}") println("Inner gets outer: ${inner.getOuterReference().outerValue}") // 11. Data classes data class Product(val id: Int, val name: String, val price: Double) { // Can have additional members fun formattedPrice() = "$${"%.2f".format(price)}" } val product1 = Product(1, "Laptop", 999.99) val product2 = Product(1, "Laptop", 999.99) val product3 = product1.copy(id = 2, price = 1099.99) println("Product1: $product1") println("Product1 formatted: ${product1.formattedPrice()}") println("product1 == product2: ${product1 == product2}") println("product1 === product2: ${product1 === product2}") println("Product3 (copy modified): $product3") // Destructuring val (id, name, price) = product1 println("Destructured: id=$id, name=$name, price=$price") // 12. Enum classes enum class Direction(val degrees: Int) { NORTH(0), EAST(90), SOUTH(180), WEST(270); fun opposite(): Direction { return when (this) { NORTH -> SOUTH EAST -> WEST SOUTH -> NORTH WEST -> EAST } } } val current = Direction.EAST println("Current direction: $current (${current.degrees}°)") println("Opposite direction: ${current.opposite()}") // Iterating enum values println("All directions:") for (direction in Direction.values()) { println(" $direction - ${direction.degrees}°") } // 13. Sealed classes sealed class NetworkResponse { data class Success(val data: String, val code: Int) : NetworkResponse() data class Error(val message: String, val code: Int) : NetworkResponse() object Loading : NetworkResponse() fun isSuccess(): Boolean = this is Success } fun handleResponse(response: NetworkResponse) { when (response) { is NetworkResponse.Success -> { println("Success: ${response.code} - ${response.data}") } is NetworkResponse.Error -> { println("Error: ${response.code} - ${response.message}") } NetworkResponse.Loading -> { println("Loading...") } // No else needed - compiler knows all cases are covered } } handleResponse(NetworkResponse.Success("Data", 200)) handleResponse(NetworkResponse.Error("Not Found", 404)) handleResponse(NetworkResponse.Loading) // 14. Value classes (inline classes) - Kotlin 1.5+ @JvmInline value class Password(val value: String) { init { require(value.length >= 8) { "Password must be at least 8 characters" } } fun strength(): String { return when (value.length) { in 8..11 -> "Weak" in 12..15 -> "Medium" else -> "Strong" } } } @JvmInline value class Email(val value: String) { init { require(value.contains("@")) { "Invalid email format" } } } fun authenticate(email: Email, password: Password) { println("Authenticating ${email.value} with password strength: ${password.strength()}") } val email = Email("user@example.com") val password = Password("securePass123") authenticate(email, password) // 15. Abstract classes abstract class Shape { abstract fun area(): Double abstract fun perimeter(): Double fun description() { println("Shape: area=${area()}, perimeter=${perimeter()}") } } class Square(val side: Double) : Shape() { override fun area() = side * side override fun perimeter() = 4 * side } val square = Square(5.0) square.description() println("\nClasses and Objects demonstration complete!") }

🔗 Inheritance

Inheritance in Kotlin follows the object-oriented principle of creating new classes based on existing ones, but with important safety improvements over traditional languages. Kotlin classes are final by default—they cannot be inherited unless explicitly marked `open`. This "closed by default" approach prevents fragile base class problems and encourages composition over inheritance when appropriate.

Open Classes and Methods

To allow a class to be inherited, mark it with the `open` keyword. Similarly, methods must be marked `open` to be overridable. The `override` modifier is mandatory when overriding methods (unlike Java's optional `@Override`). This explicit design makes inheritance intentions clear and prevents accidental overrides or extensions that could break base class contracts.

Primary Constructor Inheritance

When a derived class has a primary constructor, the base class must be initialized in that primary constructor using parameters passed to it. If the derived class has no primary constructor, each secondary constructor must initialize the base type using the `super` keyword. This ensures proper initialization order and prevents uninitialized base class state.

Overriding Properties

Properties can be overridden similar to methods. A property declared with `open` in the base class can be overridden in a derived class. The overriding property must have a compatible type. You can override a `val` with a `var`, but not vice versa (since `val` has only a getter, while `var` has both getter and setter). Property overrides can also customize accessors.

Calling Superclass Implementations

Within overridden members, you can call the superclass implementation using the `super` keyword. For multiple inheritance from interfaces, you can qualify `super` with the interface name in angle brackets: `super.method()`. This is necessary when a class implements multiple interfaces with default method implementations having the same signature.

Abstract Classes and Members

Abstract classes cannot be instantiated and may contain abstract members (methods or properties without implementation). Abstract members must be implemented by concrete subclasses. Unlike `open` members, abstract members don't need an implementation in the abstract class. A class with abstract members must itself be declared `abstract`.

Inheritance and Initialization Order

Kotlin follows a specific initialization order: 1) base class constructor, 2) base class init blocks, 3) base class property initializers, 4) derived class constructor, 5) derived class init blocks, 6) derived class property initializers. This order ensures that base class members are initialized before derived class code accesses them.

Delegation as Alternative to Inheritance

Kotlin supports class delegation using the `by` keyword, which allows implementing interfaces by delegating to another object. This is a powerful alternative to inheritance for code reuse, especially when multiple inheritance would be needed. Delegation follows the composition-over-inheritance principle and avoids deep class hierarchies.

// Inheritance Examples fun main() { println("=== Inheritance in Kotlin ===") // 1. Open classes and methods open class Animal(val name: String) { // Class must be open to inherit open fun makeSound() { // Method must be open to override println("$name makes a sound") } // Not open - cannot be overridden fun eat() { println("$name is eating") } open val sound: String = "Generic animal sound" } class Dog(name: String, val breed: String) : Animal(name) { // Must use override keyword override fun makeSound() { println("$name the $breed barks!") } // Can call superclass implementation fun makeGenericSound() { super.makeSound() } // Overriding property override val sound: String = "Woof!" } val dog = Dog("Rex", "Golden Retriever") dog.makeSound() // "Rex the Golden Retriever barks!" dog.makeGenericSound() // "Rex makes a sound" dog.eat() // "Rex is eating" (inherited, not overridden) println("Dog sound: ${dog.sound}") // 2. Primary constructor inheritance open class Vehicle(val wheels: Int, val color: String) { init { println("Vehicle with $wheels wheels created") } open fun drive() { println("Driving $color vehicle with $wheels wheels") } } class Car(color: String) : Vehicle(4, color) { // Car constructor passes arguments to Vehicle constructor init { println("Car created") } override fun drive() { println("Driving $color car") super.drive() // Call parent implementation } } val car = Car("Red") car.drive() // 3. Secondary constructors with inheritance open class Person(val name: String) { var age: Int = 0 constructor(name: String, age: Int) : this(name) { this.age = age println("Person $name created with age $age") } open fun introduce() { println("Hi, I'm $name") } } class Employee : Person { var salary: Double = 0.0 // Secondary constructor calling superclass secondary constructor constructor(name: String, age: Int, salary: Double) : super(name, age) { this.salary = salary println("Employee $name with salary $$salary created") } override fun introduce() { super.introduce() println("I earn $$salary per year") } } val employee = Employee("Alice", 30, 75000.0) employee.introduce() // 4. Property overriding open class Shape { open val vertexCount: Int = 0 open val name: String = "Shape" open fun description() { println("This is a $name with $vertexCount vertices") } } class Rectangle : Shape() { // Override val with val override val vertexCount: Int = 4 // Override val with var (allowed) override var name: String = "Rectangle" set(value) { field = value.capitalize() } // Custom property not in parent val area: Double = 0.0 // Would be calculated in real implementation } val rect = Rectangle() rect.name = "square" // Uses custom setter rect.description() // 5. Abstract classes abstract class Graphic { abstract val x: Int abstract val y: Int abstract fun draw() // Can have non-abstract members fun move(dx: Int, dy: Int) { println("Moving from ($x, $y) to (${x + dx}, ${y + dy})") } } class Circle(override val x: Int, override val y: Int, val radius: Int) : Graphic() { override fun draw() { println("Drawing circle at ($x, $y) with radius $radius") } } val circle = Circle(10, 20, 5) circle.draw() circle.move(5, -5) // 6. Multiple inheritance with interfaces interface Clickable { fun click() fun show() = println("Showing clickable") // Default implementation } interface Draggable { fun drag() fun show() = println("Showing draggable") // Default implementation with same name } class UIElement : Clickable, Draggable { override fun click() { println("UI element clicked") } override fun drag() { println("UI element dragged") } // Must override show() because both interfaces have default implementation override fun show() { super.show() // Call specific parent implementation super.show() println("UI element show") } } val element = UIElement() element.click() element.drag() element.show() // 7. Inheritance initialization order open class Base(val name: String) { init { println("Base init block: $name") } val baseProperty = "Base property".also { println("Base property initialized: $it") } } class Derived(name: String, val value: Int) : Base(name) { init { println("Derived init block: $name, value=$value") } val derivedProperty = "Derived property".also { println("Derived property initialized: $it") } } println("\nInitialization order demonstration:") val derived = Derived("Test", 42) /* Output order: Base init block: Test Base property initialized: Base property Derived init block: Test, value=42 Derived property initialized: Derived property */ // 8. Sealed class inheritance sealed class Expression { abstract fun evaluate(): Double } class Number(val value: Double) : Expression() { override fun evaluate() = value } class Sum(val left: Expression, val right: Expression) : Expression() { override fun evaluate() = left.evaluate() + right.evaluate() } class Product(val left: Expression, val right: Expression) : Expression() { override fun evaluate() = left.evaluate() * right.evaluate() } fun printExpression(expr: Expression) { when (expr) { is Number -> println("Number: ${expr.value}") is Sum -> println("Sum: ${expr.left.evaluate()} + ${expr.right.evaluate()}") is Product -> println("Product: ${expr.left.evaluate()} * ${expr.right.evaluate()}") // No else needed - compiler knows all subclasses } } val expression = Sum(Number(5.0), Product(Number(2.0), Number(3.0))) printExpression(expression) println("Result: ${expression.evaluate()}") // 9. Delegation instead of inheritance interface Printer { fun printDocument(document: String) } class LaserPrinter : Printer { override fun printDocument(document: String) { println("Laser printing: $document") } } // Using delegation instead of inheritance class OfficePrinter(private val printer: Printer) : Printer by printer { // Can add additional functionality fun printWithHeader(document: String) { println("=== Office Document ===") printDocument(document) println("=== End of Document ===") } // Can override delegated methods override fun printDocument(document: String) { println("Logging print job") printer.printDocument(document) println("Print job completed") } } val officePrinter = OfficePrinter(LaserPrinter()) officePrinter.printDocument("Report.pdf") officePrinter.printWithHeader("Memo.txt") // 10. Final modifier open class Parent { // This method is open but can be made final in subclass open fun canBeOverridden() { println("Parent implementation") } } class Child : Parent() { // Override but make it final so further subclasses can't override final override fun canBeOverridden() { println("Child implementation (final)") } } class GrandChild : Child() { // Can't override - method is final in Child // override fun canBeOverridden() { } // Error } // 11. Visibility in inheritance open class VisibilityBase { public val publicProp = "Public" protected val protectedProp = "Protected" internal val internalProp = "Internal" private val privateProp = "Private" protected open fun protectedMethod() { println("Base protected method") } } class VisibilityDerived : VisibilityBase() { // Can access protected members fun testAccess() { println(publicProp) println(protectedProp) // OK - protected is accessible in subclass println(internalProp) // println(privateProp) // Error - private not accessible protectedMethod() // OK } // Can override protected method override fun protectedMethod() { super.protectedMethod() println("Derived protected method") } } val derivedVis = VisibilityDerived() derivedVis.testAccess() // derivedVis.protectedProp // Error - protected not accessible outside println("\nInheritance demonstration complete!") }

📜 Interfaces

Interfaces in Kotlin are powerful constructs that define contracts for classes while supporting default implementations, properties, and inheritance. Unlike Java 7 interfaces (which could only have abstract methods), Kotlin interfaces can contain method implementations, properties with accessors, and can inherit from other interfaces. This flexibility makes interfaces central to Kotlin's design, often replacing abstract classes.

Interface Declaration and Implementation

Interfaces are declared using the `interface` keyword and can contain abstract method declarations, method implementations (default methods), abstract properties, and properties with accessor implementations. Classes implement interfaces using the `:` syntax, similar to extending classes. A class can implement multiple interfaces, supporting true multiple inheritance of type (though not state).

Properties in Interfaces

Interfaces can contain abstract property declarations (without initialization) or properties with custom accessors. Since interfaces cannot store state, properties in interfaces cannot have backing fields. Abstract properties must be implemented by implementing classes. Properties with accessors provide default implementations that can be overridden.

Default Methods and Multiple Inheritance

Kotlin interfaces can provide default implementations for methods. When a class implements multiple interfaces with conflicting default method signatures, it must provide its own implementation. The `super.method()` syntax allows calling a specific parent interface's implementation. This resolves the diamond problem that occurs with multiple inheritance.

Functional Interfaces (SAM Interfaces)

Interfaces with a single abstract method (SAM) can be implemented using lambda expressions for conciseness. The `fun` keyword modifier creates functional interfaces explicitly. When a functional interface is expected as a parameter, you can pass a lambda, and Kotlin automatically converts it to an instance of the interface. This is particularly useful for callback patterns.

Interface Inheritance

Interfaces can inherit from other interfaces, forming interface hierarchies. A derived interface can add new members or override members from parent interfaces. When overriding a member with a default implementation, the `override` keyword is required. Interface inheritance allows building complex type hierarchies without the limitations of class inheritance.

Delegated Interface Implementation

Kotlin's `by` keyword allows delegating interface implementation to another object. This is useful for implementing the decorator pattern or when you want to reuse implementation without inheritance. The delegating class can still override some methods while delegating others. This provides flexible composition patterns.

Interface vs Abstract Class

Interfaces cannot store state (no backing fields), while abstract classes can. Interfaces support multiple inheritance, while classes (including abstract) support single inheritance. Abstract classes can have constructors, while interfaces cannot. The choice depends on whether you need state storage, multiple inheritance, or initialization logic.

// Interfaces Examples fun main() { println("=== Interfaces in Kotlin ===") // 1. Basic interface interface Drawable { fun draw() // Abstract method fun describe() { // Method with default implementation println("I'm drawable") } } class Circle : Drawable { override fun draw() { println("Drawing a circle") } } val circle = Circle() circle.draw() circle.describe() // Uses default implementation // 2. Interfaces with properties interface Shape { val vertexCount: Int // Abstract property val name: String // Property with getter implementation get() = "Shape" fun area(): Double // Abstract method fun perimeter(): Double { // Default implementation println("Default perimeter calculation for $name") return 0.0 } } class Triangle : Shape { override val vertexCount: Int = 3 override val name: String get() = "Triangle" val base: Double = 10.0 val height: Double = 5.0 override fun area(): Double { return 0.5 * base * height } // Can override default implementation override fun perimeter(): Double { println("Triangle perimeter calculation") return base * 3 // Simplified - equilateral triangle } } val triangle = Triangle() println("${triangle.name} has ${triangle.vertexCount} vertices") println("Area: ${triangle.area()}") println("Perimeter: ${triangle.perimeter()}") // 3. Multiple interface implementation interface Clickable { fun click() fun show() = println("Showing clickable") } interface Draggable { fun drag() fun show() = println("Showing draggable") // Same method name as Clickable } class Button : Clickable, Draggable { override fun click() { println("Button clicked") } override fun drag() { println("Button dragged") } // Must override show() because of conflict override fun show() { super.show() // Call Clickable's show super.show() // Call Draggable's show println("Button show") } } val button = Button() button.click() button.drag() button.show() // 4. Interface inheritance interface Animal { fun makeSound() } interface Pet : Animal { val name: String fun play() { println("$name is playing") } // Can override parent interface method with default override fun makeSound() { println("$name makes a pet sound") } } interface Mammal : Animal { fun feedYoung() } class Dog(override val name: String) : Pet, Mammal { // Implements makeSound() from Animal (through Pet) // Could override if needed override fun feedYoung() { println("$name is feeding puppies") } // Can add additional methods fun bark() { println("$name barks!") } } val dog = Dog("Buddy") dog.makeSound() // From Pet's default implementation dog.play() dog.feedYoung() dog.bark() // 5. Functional interfaces (SAM - Single Abstract Method) fun interface StringProcessor { fun process(input: String): String // Single abstract method } // Implementation using lambda val uppercaseProcessor = StringProcessor { it.uppercase() } val reverseProcessor = StringProcessor { it.reversed() } println("Uppercase: ${uppercaseProcessor.process("hello")}") println("Reversed: ${reverseProcessor.process("hello")}") // Traditional implementation class PrefixProcessor(val prefix: String) : StringProcessor { override fun process(input: String): String { return "$prefix$input" } } val prefixProcessor = PrefixProcessor(">>> ") println("Prefixed: ${prefixProcessor.process("hello")}") // 6. Using SAM interfaces as parameters fun processText(text: String, processor: StringProcessor): String { return processor.process(text) } val result1 = processText("kotlin", { it.capitalize() }) // Lambda as SAM val result2 = processText("kotlin") { it + " is awesome!" } // Trailing lambda println("Processed: $result1, $result2") // 7. Interface delegation interface Database { fun save(data: String) fun load(id: Int): String? } class RealDatabase : Database { private val storage = mutableMapOf() private var nextId = 1 override fun save(data: String) { storage[nextId++] = data println("Saved '$data' with id ${nextId - 1}") } override fun load(id: Int): String? { return storage[id].also { if (it != null) println("Loaded '$it' with id $id") else println("No data found for id $id") } } } // Decorator using delegation class LoggingDatabase(private val database: Database) : Database by database { override fun save(data: String) { println("LOG: About to save data") database.save(data) println("LOG: Save completed") } override fun load(id: Int): String? { println("LOG: About to load data with id $id") val result = database.load(id) println("LOG: Load completed") return result } } val realDb = RealDatabase() val loggingDb = LoggingDatabase(realDb) loggingDb.save("First record") loggingDb.save("Second record") println("Loaded: ${loggingDb.load(1)}") println("Loaded: ${loggingDb.load(99)}") // 8. Properties with setters in interfaces interface Configurable { var setting: String // Abstract property var description: String // Property with custom accessors get() = "Configurable with setting: $setting" set(value) { println("Description set to: $value") // Can't have backing field, but can store in implementing class } } class Configuration : Configurable { override var setting: String = "default" private var customDescription: String = "" override var description: String get() = customDescription set(value) { customDescription = "Custom: $value" } } val config = Configuration() config.setting = "production" config.description = "Production configuration" println(config.description) // 9. Interface with companion object interface Factory { companion object { fun create(): Factory { return object : Factory { override fun createInstance(): Any { return "Created instance" } } } } fun createInstance(): Any } val factory = Factory.create() println("Factory created: ${factory.createInstance()}") // 10. Resolving override conflicts with super interface A { fun foo() { println("A.foo") } fun bar() } interface B { fun foo() { println("B.foo") } fun bar() { println("B.bar") } } class C : A, B { // Must override foo() due to conflict override fun foo() { super.foo() // Call A's implementation super.foo() // Call B's implementation println("C.foo") } // Must override bar() even though B has default // (because A.bar() is abstract) override fun bar() { super.bar() // Can use B's default println("C.bar") } } val c = C() c.foo() c.bar() // 11. Interface as type interface Vehicle { fun drive() } class Car : Vehicle { override fun drive() { println("Car driving") } fun honk() { println("Beep beep!") } } class Bike : Vehicle { override fun drive() { println("Bike pedaling") } fun ringBell() { println("Ring ring!") } } fun testVehicle(vehicle: Vehicle) { vehicle.drive() // Can't call honk() or ringBell() here - only know it's a Vehicle } val vehicles: List = listOf(Car(), Bike()) for (vehicle in vehicles) { testVehicle(vehicle) // Type checks and smart casts when (vehicle) { is Car -> vehicle.honk() is Bike -> vehicle.ringBell() } } // 12. Read-only vs mutable properties in interfaces interface ReadOnly { val value: String // Read-only in interface fun getValue(): String } interface Mutable { var value: String // Mutable in interface fun setValue(newValue: String) } class Implementation : ReadOnly, Mutable { // Must implement both property declarations override var value: String = "" override fun getValue(): String { return "Value: $value" } override fun setValue(newValue: String) { value = newValue println("Value set to: $newValue") } } val impl = Implementation() impl.setValue("Test") println(impl.getValue()) println("\nInterfaces demonstration complete!") }

🗃️ Data Classes

Data Classes in Kotlin are a concise way to create classes that primarily hold data. They automatically generate several standard functions: `equals()`, `hashCode()`, `toString()`, `copy()`, and component functions for destructuring. This eliminates boilerplate code that would otherwise be manually written in Java for value objects, making Kotlin code more readable and less error-prone.

Automatic Function Generation

When you declare a data class, Kotlin automatically generates `equals()` for structural equality comparison, `hashCode()` consistent with equals, `toString()` in a readable format, and `copy()` for creating modified copies. These functions only consider properties declared in the primary constructor, ignoring properties declared in the class body. This ensures predictable behavior.

Copy Function with Named Arguments

The `copy()` function allows creating a new instance with some properties modified. It uses default parameter values equal to the current property values, so you only need to specify properties you want to change. Named arguments make `copy()` calls self-documenting: `person.copy(name = "New Name")`. This is particularly useful for immutable data structures.

Destructuring Declarations

Data classes automatically generate component functions (`component1()`, `component2()`, etc.) corresponding to properties in the primary constructor. This enables destructuring declarations: `val (name, age) = person`. The number of variables in destructuring must match the number of component functions. Destructuring works in loops, lambda parameters, and other contexts.

Requirements and Limitations

Data classes must have a primary constructor with at least one parameter. All primary constructor parameters must be marked as `val` or `var`. Data classes cannot be abstract, open, sealed, or inner. However, they can implement interfaces and extend other classes (including other data classes, though this is discouraged due to equals/hashCode issues).

Inheritance Considerations

While data classes can extend other classes, they have restrictions regarding equality. The generated `equals()` and `hashCode()` methods only consider properties declared in the primary constructor, ignoring inherited properties. This can lead to unexpected behavior if the parent class has properties that should affect equality. Generally, data classes work best as leaf classes in hierarchies.

Standard Data Class Patterns

Data classes are ideal for DTOs (Data Transfer Objects), value objects, result types, event objects, and configuration objects. They pair well with sealed classes for representing algebraic data types. Common patterns include using data classes with `copy()` for immutable updates, with destructuring for pattern matching, and with collections for complex data structures.

Customizing Generated Functions

You can customize generated functions by providing explicit implementations. If you override `toString()`, `equals()`, or `hashCode()`, the compiler won't generate these functions. The `copy()` function cannot be overridden. You can add additional properties and methods in the class body, but they won't be included in the generated functions.

// Data Classes Examples fun main() { println("=== Data Classes in Kotlin ===") // 1. Basic data class data class Person(val name: String, val age: Int) val person1 = Person("Alice", 30) val person2 = Person("Alice", 30) val person3 = Person("Bob", 25) // toString() auto-generated println("person1: $person1") // Person(name=Alice, age=30) // equals() auto-generated (structural equality) println("person1 == person2: ${person1 == person2}") // true println("person1 == person3: ${person1 == person3}") // false // hashCode() auto-generated println("person1.hashCode(): ${person1.hashCode()}") println("person2.hashCode(): ${person2.hashCode()}") println("Same hash codes: ${person1.hashCode() == person2.hashCode()}") // 2. Copy function val person4 = person1.copy() // Exact copy println("person1 copied: $person4") val person5 = person1.copy(name = "Alicia") // Copy with modification println("person1 copied with new name: $person5") val person6 = person1.copy(age = 31) // Copy with different age println("person1 copied with new age: $person6") // 3. Destructuring declarations val (name, age) = person1 // Destructuring println("Destructured: name=$name, age=$age") // Component functions (auto-generated) println("Component 1: ${person1.component1()}") // name println("Component 2: ${person1.component1()}") // age // Destructuring in loops val people = listOf(person1, person3, person5) println("\nPeople list:") for ((personName, personAge) in people) { println(" $personName is $personAge years old") } // 4. Data class with default values data class Product( val id: Int, val name: String, val price: Double = 0.0, val inStock: Boolean = true ) val product1 = Product(1, "Laptop", 999.99) val product2 = Product(2, "Mouse", inStock = false) println("\nProducts:") println("product1: $product1") println("product2: $product2") // 5. Data class with additional properties/methods data class User( val id: Int, val username: String, val email: String ) { // Additional property (not in primary constructor) val displayName: String get() = "@$username" // Additional method fun sendMessage(message: String) { println("Sending '$message' to $email") } // Note: displayName and sendMessage are NOT included in // generated equals(), hashCode(), toString(), copy(), or destructuring } val user1 = User(1, "alice", "alice@example.com") val user2 = User(1, "alice", "alice@example.com") println("\nUsers:") println("user1: $user1") println("user2: $user2") println("user1 == user2: ${user1 == user2}") // true (ignores displayName) println("user1.displayName: ${user1.displayName}") user1.sendMessage("Hello!") // 6. Nested destructuring data class Name(val first: String, val last: String) data class Employee(val id: Int, val name: Name, val department: String) val employee = Employee(101, Name("John", "Doe"), "Engineering") // Simple destructuring val (empId, empName, dept) = employee println("\nEmployee: id=$empId, name=$empName, department=$dept") // Nested destructuring val (id2, Name(firstName, lastName), department2) = employee println("Nested destructuring: $firstName $lastName works in $department2") // 7. Data classes in collections data class Point(val x: Int, val y: Int) val points = listOf( Point(0, 0), Point(1, 1), Point(2, 4), Point(3, 9), Point(4, 16) ) println("\nPoints:") points.forEach { println(" $it") } // Using data class properties in collection operations val xValues = points.map { it.x } val yValues = points.map { it.y } val sumOfX = points.sumOf { it.x } val maxY = points.maxOf { it.y } println("X values: $xValues") println("Y values: $yValues") println("Sum of X: $sumOfX") println("Max Y: $maxY") // Finding duplicates (uses equals()) val duplicatePoints = listOf(Point(1, 1), Point(2, 4), Point(1, 1)) val uniquePoints = duplicatePoints.toSet() // Set uses hashCode() and equals() println("Unique points: $uniquePoints") // 8. Copy with nested data classes data class Address(val street: String, val city: String, val zip: String) data class Customer(val id: Int, val name: String, val address: Address) val customer1 = Customer(1, "Alice", Address("123 Main St", "NYC", "10001")) val customer2 = customer1.copy( address = customer1.address.copy(city = "Boston", zip = "02101") ) println("\nCustomers:") println("Original: $customer1") println("Modified address: $customer2") // 9. Data class limitations and inheritance open class Entity(val id: Int) data class ProductEntity( val productName: String, val price: Double ) : Entity(1) // Extending a class val prod1 = ProductEntity("Laptop", 999.99) val prod2 = ProductEntity("Laptop", 999.99) // Warning: equals() and hashCode() only consider productName and price, not id! println("prod1 == prod2: ${prod1 == prod2}") // true (ignores inherited id) println("prod1.id == prod2.id: ${prod1.id == prod2.id}") // true (both 1) // 10. Data class with custom toString data class Book(val title: String, val author: String, val year: Int) { // Override auto-generated toString override fun toString(): String { return "\"$title\" by $author ($year)" } // Can also override equals and hashCode if needed // override fun equals(other: Any?): Boolean { ... } // override fun hashCode(): Int { ... } // Note: copy() cannot be overridden } val book = Book("1984", "George Orwell", 1949) println("\nBook: $book") // 11. Data class as return type (pair with sealed class) sealed class Result data class Success(val data: String) : Result() data class Error(val message: String, val code: Int) : Result() fun processResult(result: Result) { when (result) { is Success -> println("Success: ${result.data}") is Error -> println("Error ${result.code}: ${result.message}") } } processResult(Success("Data loaded")) processResult(Error("Not found", 404)) // 12. Data class with var properties (mutable) data class MutablePerson(var name: String, var age: Int) val mutablePerson = MutablePerson("Charlie", 40) println("\nMutable person: $mutablePerson") mutablePerson.name = "Charles" mutablePerson.age = 41 println("After mutation: $mutablePerson") // Warning: mutable data classes can have issues with collections // if modified while in a collection that uses hashCode() // 13. Component functions beyond destructuring data class TripleData(val a: String, val b: String, val c: String) val triple = TripleData("first", "second", "third") // Component functions available println("\nTriple components:") println("component1(): ${triple.component1()}") println("component2(): ${triple.component2()}") println("component3(): ${triple.component3()}") // Using in lambda with destructuring val triples = listOf(triple, TripleData("x", "y", "z")) triples.forEach { (a, b, c) -> println(" $a, $b, $c") } // 14. Real-world example: API response modeling data class ApiResponse( val success: Boolean, val data: T?, val error: String? = null, val timestamp: Long = System.currentTimeMillis() ) data class UserResponse( val id: Int, val name: String, val email: String ) val successResponse = ApiResponse( success = true, data = UserResponse(1, "Alice", "alice@example.com") ) val errorResponse = ApiResponse( success = false, data = null, error = "User not found" ) println("\nAPI Responses:") println("Success: $successResponse") println("Error: $errorResponse") // Processing response fun handleResponse(response: ApiResponse) { if (response.success) { val user = response.data!! println("User loaded: ${user.name} (${user.email})") } else { println("Error: ${response.error}") } } handleResponse(successResponse) handleResponse(errorResponse) println("\nData classes demonstration complete!") }

⚡ Functional Programming

Functional Programming (FP) in Kotlin is supported as a first-class paradigm alongside object-oriented programming. Kotlin incorporates key FP concepts like higher-order functions, lambdas, immutability, and pure functions while maintaining seamless interoperability with Java. The language's design encourages functional patterns that lead to more expressive, concise, and maintainable code.

Functions as First-Class Citizens

In Kotlin, functions can be assigned to variables, passed as arguments, returned from other functions, and stored in data structures. This enables higher-order functions—functions that take other functions as parameters or return functions. The type system represents function types with arrow notation: `(Int, Int) -> Int` for a function taking two Ints and returning an Int.

Lambda Expressions and Anonymous Functions

Lambda expressions provide a concise way to define anonymous functions. The syntax `{ parameters -> body }` creates a function value. When a lambda has a single parameter, it can be omitted and referred to as `it`. Lambda expressions can capture variables from their enclosing scope. Anonymous functions (`fun(x: Int): Int { return x * x }`) are similar but allow explicit return types and return statements.

Higher-Order Functions and Function Types

Higher-order functions operate on other functions. Common examples include `map`, `filter`, and `reduce` on collections. Kotlin's standard library is rich with higher-order functions for collection processing, scope management, and resource handling. Function types can have receiver types (for extension functions), nullable returns, and generic parameters.

Immutability and Pure Functions

Kotlin encourages immutability through `val` declarations and immutable collections (`List`, `Set`, `Map`). Pure functions—functions whose output depends only on inputs and have no side effects—are easier to reason about and test. While Kotlin doesn't enforce purity, it provides the tools to write pure functions when desired.

Function Composition and Currying

Functions can be composed using the `compose` and `andThen` operations (or custom implementations). Currying transforms a multi-parameter function into a series of single-parameter functions. While Kotlin doesn't have built-in currying, it can be implemented using higher-order functions. These techniques enable powerful functional abstractions.

Recursion and Tail Recursion

Kotlin supports recursion, and when functions are marked with `tailrec`, the compiler optimizes tail-recursive calls into loops, preventing stack overflow. This allows recursive algorithms to be expressed naturally while maintaining performance. Tail recursion requires the recursive call to be the last operation in the function.

Monads and Functional Patterns

Kotlin's standard library includes types that follow monadic patterns: `Result` (for error handling), `Option`-like behavior with nullable types, and sequences for lazy evaluation. While not explicitly called monads, these types enable functional error handling, transformation pipelines, and declarative data processing similar to FP languages.

// Functional Programming Examples fun main() { println("=== Functional Programming in Kotlin ===") // 1. Function types and variables val add: (Int, Int) -> Int = { a, b -> a + b } val multiply = { a: Int, b: Int -> a * b } val square: (Int) -> Int = { it * it } val greet: () -> String = { "Hello, World!" } println("add(3, 5): ${add(3, 5)}") println("multiply(4, 6): ${multiply(4, 6)}") println("square(7): ${square(7)}") println("greet(): ${greet()}") // 2. Higher-order functions fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int { return operation(x, y) } val result1 = calculate(10, 5) { a, b -> a + b } val result2 = calculate(10, 5) { a, b -> a - b } val result3 = calculate(10, 5, multiply) println("10 + 5 = $result1") println("10 - 5 = $result2") println("10 * 5 = $result3") // 3. Functions returning functions fun multiplier(factor: Int): (Int) -> Int { return { number -> number * factor } } val double = multiplier(2) val triple = multiplier(3) println("double(5): ${double(5)}") println("triple(5): ${triple(5)}") // 4. Lambda expressions val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // Filter even numbers val evens = numbers.filter { it % 2 == 0 } println("Evens: $evens") // Map to squares val squares = numbers.map { it * it } println("Squares: $squares") // Reduce to sum val sum = numbers.reduce { acc, num -> acc + num } println("Sum: $sum") // Fold with initial value val product = numbers.fold(1) { acc, num -> acc * num } println("Product: $product") // 5. It parameter (single parameter lambda) val strings = listOf("apple", "banana", "cherry", "date") val uppercase = strings.map { it.uppercase() } val longStrings = strings.filter { it.length > 5 } val lengths = strings.map { it.length } println("Uppercase: $uppercase") println("Long strings: $longStrings") println("Lengths: $lengths") // 6. Anonymous functions val isEven = fun(n: Int): Boolean { return n % 2 == 0 } val cube = fun(n: Int): Int { return n * n * n } println("isEven(4): ${isEven(4)}") println("cube(3): ${cube(3)}") // Anonymous function as argument (allows explicit return) val result = calculate(8, 2, fun(a, b): Int { return a * a + b * b }) println("8^2 + 2^2 = $result") // 7. Function composition fun compose(f: (B) -> C, g: (A) -> B): (A) -> C { return { x -> f(g(x)) } } val addTwo = { x: Int -> x + 2 } val timesThree = { x: Int -> x * 3 } val addThenMultiply = compose(timesThree, addTwo) val multiplyThenAdd = { x: Int -> timesThree(addTwo(x)) } // Manual composition println("addThenMultiply(5): ${addThenMultiply(5)}") // (5 + 2) * 3 = 21 println("multiplyThenAdd(5): ${multiplyThenAdd(5)}") // Same // 8. Currying fun addCurried(a: Int): (Int) -> Int { return { b -> a + b } } val addFive = addCurried(5) println("addFive(3): ${addFive(3)}") // 8 // Generic currying fun curry(f: (A, B) -> C): (A) -> (B) -> C { return { a -> { b -> f(a, b) } } } val curriedAdd = curry { a: Int, b: Int -> a + b } val addTen = curriedAdd(10) println("addTen(7): ${addTen(7)}") // 17 // 9. Partial application fun power(base: Int, exponent: Int): Int { return Math.pow(base.toDouble(), exponent.toDouble()).toInt() } // Partial application using lambda val square2 = { x: Int -> power(x, 2) } val cube2 = { x: Int -> power(x, 3) } println("square2(4): ${square2(4)}") println("cube2(3): ${cube2(3)}") // 10. Immutability val immutableList = listOf(1, 2, 3) // Read-only list // immutableList.add(4) // Compilation error val mutableList = mutableListOf(1, 2, 3) // Mutable list mutableList.add(4) // OK // Functional transformation creates new collections val doubled = immutableList.map { it * 2 } // New list created println("Original: $immutableList, Doubled: $doubled") // 11. Pure functions // Pure function: output depends only on inputs, no side effects fun pureAdd(a: Int, b: Int): Int { return a + b } // Impure function: has side effect (printing) fun impureAdd(a: Int, b: Int): Int { println("Adding $a and $b") return a + b } // Impure function: depends on external state var counter = 0 fun incrementAndGet(): Int { return ++counter } println("Pure add: ${pureAdd(3, 4)}") println("Impure add: ${impureAdd(3, 4)}") println("Increment: ${incrementAndGet()}, ${incrementAndGet()}") // 12. Recursion and tail recursion // Regular recursion (risk of stack overflow for large n) fun factorial(n: Int): Int { return if (n <= 1) 1 else n * factorial(n - 1) } // Tail recursion (optimized to loop by compiler) tailrec fun factorialTail(n: Int, accumulator: Int = 1): Int { return if (n <= 1) accumulator else factorialTail(n - 1, n * accumulator) } println("factorial(5): ${factorial(5)}") println("factorialTail(5): ${factorialTail(5)}") // 13. Function references fun isPositive(n: Int) = n > 0 fun negate(n: Int) = -n val numbers2 = listOf(-3, -2, -1, 0, 1, 2, 3) val positives = numbers2.filter(::isPositive) val negated = numbers2.map(::negate) println("Positives: $positives") println("Negated: $negated") // Method reference on instance val str = "Kotlin" val lengthFunc: (String) -> Int = String::length println("Length of 'Kotlin': ${lengthFunc(str)}") // 14. Function pipelines data class Person(val name: String, val age: Int) val people = listOf( Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35), Person("Diana", 28), Person("Eve", 40) ) // Functional pipeline val resultPipeline = people .filter { it.age >= 30 } // Keep people 30+ .sortedBy { it.name } // Sort by name .map { it.name.uppercase() } // Convert to uppercase .joinToString(", ") // Join to string println("People 30+ (sorted, uppercase): $resultPipeline") // 15. Lazy evaluation with sequences val bigList = (1..1_000_000).toList() // Eager evaluation (processes entire collection) val eagerResult = bigList .filter { it % 2 == 0 } .map { it * it } .take(10) .toList() // Lazy evaluation with Sequence (processes only needed elements) val lazyResult = bigList.asSequence() .filter { println("Filtering $it") it % 2 == 0 } .map { println("Mapping $it") it * it } .take(10) .toList() println("First 10 even squares (eager): $eagerResult") println("First 10 even squares (lazy): $lazyResult") // 16. Monadic patterns (Result type) sealed class Result { data class Success(val value: T) : Result() data class Error(val message: String) : Result() } fun parseNumber(str: String): Result { return try { Result.Success(str.toInt()) } catch (e: NumberFormatException) { Result.Error("Cannot parse '$str' as number") } } fun doubleIfPositive(n: Int): Result { return if (n > 0) { Result.Success(n * 2) } else { Result.Error("Number must be positive") } } // Function composition with Result fun parseAndDouble(str: String): Result { return when (val parsed = parseNumber(str)) { is Result.Success -> doubleIfPositive(parsed.value) is Result.Error -> parsed } } println("parseAndDouble(\"10\"): ${parseAndDouble("10")}") println("parseAndDouble(\"-5\"): ${parseAndDouble("-5")}") println("parseAndDouble(\"abc\"): ${parseAndDouble("abc")}") // 17. Functional data structures // Persistent/immutable data structure pattern data class FunctionalList( val head: T, val tail: FunctionalList? = null ) { fun add(newHead: T): FunctionalList { return FunctionalList(newHead, this) } fun size(): Int { return 1 + (tail?.size() ?: 0) } fun toList(): List { val result = mutableListOf() var current: FunctionalList? = this while (current != null) { result.add(current.head) current = current.tail } return result } } val functionalList = FunctionalList(1) .add(2) .add(3) .add(4) println("Functional list size: ${functionalList.size()}") println("Functional list as list: ${functionalList.toList()}") // Original unchanged val newList = functionalList.add(5) println("Original: ${functionalList.toList()}") println("New: ${newList.toList()}") println("\nFunctional programming demonstration complete!") }

λ Lambda Expressions

Lambda expressions in Kotlin are concise anonymous functions that can be passed as arguments, stored in variables, or returned from functions. They are a fundamental feature enabling functional programming patterns in Kotlin. Lambdas provide a more readable and expressive alternative to anonymous classes for defining behavior inline, especially when working with collections, event handlers, or callback functions.

Lambda Syntax and Structure

Lambda expressions in Kotlin follow the syntax: `{ parameters -> body }`. Parameters (with optional types) are on the left of the arrow (`->`), and the body containing expressions is on the right. If the lambda has no parameters, the arrow can be omitted. When the inferred return type is not `Unit`, the last expression in the body becomes the return value. Lambdas can span multiple lines, with the last expression determining the return value.

Implicit `it` Parameter

When a lambda has exactly one parameter and the type can be inferred, the parameter can be omitted and referred to by the implicit name `it`. This makes simple lambdas extremely concise: `list.map { it * 2 }` instead of `list.map { x -> x * 2 }`. The `it` convention is widely used in Kotlin but should be avoided when nesting lambdas or when the parameter name would provide clarity.

Trailing Lambdas

When a function's last parameter is a function type, the lambda argument can be placed outside the parentheses—this is called a trailing lambda. If the lambda is the only argument, parentheses can be omitted entirely. This syntax enables DSL-like constructs: `with(list) { add("item") }` instead of `with(list, { add("item") })`. Trailing lambdas make code more readable, especially with scope functions and builders.

Lambda with Receiver

Kotlin supports lambdas with receivers, which have access to members of a receiver object within the lambda body. The type is written as `ReceiverType.() -> ReturnType`. Inside the lambda, `this` refers to the receiver object, and members can be accessed without qualification. This feature powers DSL construction, scope functions (`apply`, `run`), and type-safe builders.

Capturing Variables

Lambdas can capture variables from their enclosing scope. Unlike Java (which requires effectively final variables), Kotlin allows lambdas to modify captured mutable variables. The captured variables are stored with the lambda, extending their lifetime. This enables powerful patterns like accumulators in higher-order functions but requires caution with concurrency.

SAM Conversions for Java Interoperability

When interfacing with Java code that expects a SAM (Single Abstract Method) interface, Kotlin can automatically convert a lambda to an instance of that interface. This works for Java interfaces with exactly one abstract method. The conversion is automatic when passing a lambda where a SAM interface is expected, eliminating the need for anonymous class syntax.

Performance Considerations

Lambdas are objects in Kotlin and incur runtime overhead. However, the `inline` keyword can eliminate this overhead by inlining the lambda's bytecode at the call site. The standard library's higher-order functions like `map`, `filter`, and `let` are inline functions. For performance-critical code with complex lambdas, consider using `inline` functions or function references.

// Lambda Expressions Examples fun main() { println("=== Lambda Expressions in Kotlin ===") // 1. Basic lambda syntax val add: (Int, Int) -> Int = { a: Int, b: Int -> a + b } val multiply = { a: Int, b: Int -> a * b } val greet = { name: String -> "Hello, $name!" } val noParam = { "I have no parameters" } println("add(3, 5): ${add(3, 5)}") println("multiply(4, 6): ${multiply(4, 6)}") println("greet(\"Alice\"): ${greet("Alice")}") println("noParam(): ${noParam()}") // 2. Type inference in lambdas val inferredAdd = { a: Int, b: Int -> a + b } // Type inferred from parameters val length: (String) -> Int = { it.length } // Type declared, parameter inferred println("inferredAdd(2, 3): ${inferredAdd(2, 3)}") println("length(\"Kotlin\"): ${length("Kotlin")}") // 3. Lambda as function parameter fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int { return operation(x, y) } val sum = calculate(10, 5) { a, b -> a + b } val difference = calculate(10, 5) { a, b -> a - b } val product = calculate(10, 5) { a, b -> a * b } println("10 + 5 = $sum") println("10 - 5 = $difference") println("10 * 5 = $product") // 4. Implicit 'it' parameter val numbers = listOf(1, 2, 3, 4, 5) val doubled = numbers.map { it * 2 } val evens = numbers.filter { it % 2 == 0 } val sumOfSquares = numbers.fold(0) { acc, num -> acc + num * num } println("Numbers: $numbers") println("Doubled: $doubled") println("Evens: $evens") println("Sum of squares: $sumOfSquares") // 5. Trailing lambdas fun withPrefix(prefix: String, block: () -> String): String { return "$prefix: ${block()}" } // Regular call val result1 = withPrefix("Info", { "Hello" }) // Trailing lambda (lambda outside parentheses) val result2 = withPrefix("Info") { "Hello" } // When lambda is the only parameter fun runBlock(block: () -> Unit) { println("Running block...") block() } runBlock { println("Inside lambda") println("Another statement") } println("Result1: $result1") println("Result2: $result2") // 6. Lambda with receiver class StringBuilder { private val buffer = StringBuilder() fun append(text: String) { buffer.append(text) } fun build(): String { return buffer.toString() } } fun buildString(block: StringBuilder.() -> Unit): String { val builder = StringBuilder() builder.block() // Call lambda with builder as receiver return builder.build() } val message = buildString { append("Hello, ") append("World!") } println("Built string: $message") // 7. Capturing variables var counter = 0 val incrementer = { counter++ // Lambda captures and modifies outer variable println("Counter: $counter") } incrementer() incrementer() incrementer() // Complex capture example fun makeCounter(): () -> Int { var count = 0 // Local variable captured by lambda return { count++ } // Returns lambda that captures count } val counter1 = makeCounter() val counter2 = makeCounter() println("Counter1: ${counter1()}, ${counter1()}, ${counter1()}") println("Counter2: ${counter2()}, ${counter2()}") println("Counter1 again: ${counter1()}") // 8. SAM conversion (for Java interoperability) // Java interface: interface Runnable { void run(); } val runnable = Runnable { println("Running in Runnable") } // Using with thread (common Java pattern) val thread = Thread { println("Running in thread") } runnable.run() thread.start() // 9. Lambda vs function reference fun isEven(n: Int) = n % 2 == 0 fun square(n: Int) = n * n val numbersList = listOf(1, 2, 3, 4, 5) // Using lambda val evensLambda = numbersList.filter { it % 2 == 0 } // Using function reference val evensRef = numbersList.filter(::isEven) // Using lambda for transformation val squaresLambda = numbersList.map { it * it } // Using function reference for transformation val squaresRef = numbersList.map(::square) println("Evens (lambda): $evensLambda") println("Evens (ref): $evensRef") println("Squares (lambda): $squaresLambda") println("Squares (ref): $squaresRef") // 10. Lambda with destructuring data class Point(val x: Int, val y: Int) val points = listOf(Point(1, 2), Point(3, 4), Point(5, 6)) // Destructuring in lambda parameters val xValues = points.map { (x, y) -> x } val sumCoordinates = points.map { (x, y) -> x + y } println("Points: $points") println("X values: $xValues") println("Sum of coordinates: $sumCoordinates") // 11. Inline lambdas (no performance overhead) inline fun measureTime(block: () -> Unit): Long { val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start } val time = measureTime { var sum = 0 for (i in 1..1000000) { sum += i } println("Computed sum: $sum") } println("Computation took ${time}ms") // 12. Returning from lambdas fun findFirstEven(numbers: List): Int? { numbers.forEach { if (it % 2 == 0) { return it // Returns from findFirstEven, not just lambda } } return null } // Using label to return from lambda only fun findFirstEvenLabel(numbers: List): Int? { numbers.forEach lambda@{ if (it % 2 == 0) { return@lambda // Returns only from lambda, continues loop } } return null } // Using implicit label (function name) fun findFirstEvenImplicit(numbers: List): Int? { numbers.forEach { if (it % 2 == 0) { return@forEach // Returns from lambda using function name as label } } return null } val sampleNumbers = listOf(1, 3, 5, 2, 4, 6) println("First even (regular return): ${findFirstEven(sampleNumbers)}") println("First even (labeled return): ${findFirstEvenLabel(sampleNumbers)}") println("First even (implicit label): ${findFirstEvenImplicit(sampleNumbers)}") // 13. Lambda with vararg parameters fun processNumbers(vararg numbers: Int, processor: (Int) -> Int): List { return numbers.map(processor).toList() } val processed = processNumbers(1, 2, 3, 4, 5) { it * 10 } println("Processed numbers: $processed") // 14. Lambda storage in collections val operations = mapOf Int>( "add" to { a, b -> a + b }, "subtract" to { a, b -> a - b }, "multiply" to { a, b -> a * b }, "divide" to { a, b -> a / b } ) val a = 20 val b = 5 println("Operations on $a and $b:") for ((name, operation) in operations) { println(" $name: ${operation(a, b)}") } // 15. Lambda with receiver in practice (DSL-like) class HTML { private val children = mutableListOf() fun body(block: Body.() -> Unit) { val body = Body().apply(block) children.add(body) } override fun toString(): String { return "\n${children.joinToString("\n")}\n" } } class Body { private val children = mutableListOf() fun h1(text: String) { children.add("

$text

") } fun p(text: String) { children.add("

$text

") } override fun toString(): String { return " \n ${children.joinToString("\n ")}\n " } } fun html(block: HTML.() -> Unit): HTML { return HTML().apply(block) } val htmlDoc = html { body { h1("Welcome") p("This is a paragraph") p("Another paragraph") } } println("\nGenerated HTML:") println(htmlDoc) println("\nLambda expressions demonstration complete!") }

🔄 Higher-Order Functions

Higher-Order Functions are functions that take other functions as parameters or return functions as results. This is a fundamental concept in functional programming that Kotlin embraces fully. Higher-order functions enable abstraction over behavior, leading to more generic, reusable, and declarative code. Kotlin's standard library is rich with higher-order functions, particularly for collection processing.

Function Types and Declarations

Higher-order functions rely on function types, which are declared using arrow notation: `(ParameterTypes) -> ReturnType`. Function types can have nullable returns (`() -> String?`), receiver types (`String.() -> Int`), or be nullable themselves (`((Int) -> String)?`). Higher-order functions can accept function parameters with default values, enabling flexible APIs.

Standard Library Higher-Order Functions

Kotlin's standard library provides numerous higher-order functions for collections: `map`, `filter`, `reduce`, `fold`, `flatMap`, `groupBy`, `partition`, `zip`, etc. Each takes a lambda that defines the transformation or predicate. These functions encourage a declarative style where you specify what to do rather than how to do it, leading to more readable code.

Function Composition and Pipelines

Higher-order functions enable function composition—creating new functions by combining existing ones. The `compose` and `andThen` operations (or custom implementations) chain functions together. This allows building processing pipelines where data flows through a series of transformations: `data.map(f).filter(g).reduce(h)`. Such pipelines are easy to reason about and modify.

Custom Higher-Order Functions

You can create your own higher-order functions to abstract repetitive patterns. Common patterns include: `withResource` for resource management, `retry` for operations that may fail, `time` for performance measurement, and `memoize` for caching. Custom higher-order functions capture domain-specific patterns and reduce boilerplate across your codebase.

Inline Higher-Order Functions

The `inline` keyword, when applied to higher-order functions, instructs the compiler to substitute the function's body at the call site, including the lambda bodies. This eliminates the runtime overhead of lambda objects and enables non-local returns (returning from the enclosing function). However, inline functions increase code size and cannot be recursive or use function references.

Currying and Partial Application

Currying transforms a function taking multiple arguments into a sequence of functions each taking a single argument. Partial application fixes some arguments of a function, producing a new function with fewer parameters. While not built into Kotlin, these techniques can be implemented using higher-order functions. They enable powerful functional patterns and flexible API design.

Function Builders and DSLs

Higher-order functions with receiver parameters enable building type-safe DSLs (Domain-Specific Languages). By combining lambdas with receivers and infix notation, you can create APIs that read like natural language. Examples include Kotlin's HTML builders, Gradle build scripts, and testing frameworks. These builders provide both compile-time safety and expressive syntax.

// Higher-Order Functions Examples fun main() { println("=== Higher-Order Functions in Kotlin ===") // 1. Basic higher-order function fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int { return operation(x, y) } val sum = calculate(10, 5) { a, b -> a + b } val product = calculate(10, 5) { a, b -> a * b } println("10 + 5 = $sum") println("10 * 5 = $product") // 2. Function returning function fun multiplier(factor: Int): (Int) -> Int { return { number -> number * factor } } val double = multiplier(2) val triple = multiplier(3) println("double(7) = ${double(7)}") println("triple(7) = ${triple(7)}") // 3. Function composition fun compose(f: (B) -> C, g: (A) -> B): (A) -> C { return { x -> f(g(x)) } } val addTwo = { x: Int -> x + 2 } val timesThree = { x: Int -> x * 3 } val addThenMultiply = compose(timesThree, addTwo) val multiplyThenAdd = { x: Int -> timesThree(addTwo(x)) } println("(5 + 2) * 3 = ${addThenMultiply(5)}") println("5 * 3 + 2 = ${multiplyThenAdd(5)}") // 4. Standard library higher-order functions on collections val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // map - transform each element val squares = numbers.map { it * it } println("Squares: $squares") // filter - keep elements matching predicate val evens = numbers.filter { it % 2 == 0 } println("Evens: $evens") // reduce - combine elements val sumAll = numbers.reduce { acc, num -> acc + num } println("Sum: $sumAll") // fold - reduce with initial value val productAll = numbers.fold(1) { acc, num -> acc * num } println("Product: $productAll") // flatMap - transform and flatten val nested = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6)) val flat = nested.flatMap { it } println("Flat: $flat") // groupBy - group by key val grouped = numbers.groupBy { if (it % 2 == 0) "even" else "odd" } println("Grouped: $grouped") // partition - split by predicate val (evenList, oddList) = numbers.partition { it % 2 == 0 } println("Even partition: $evenList") println("Odd partition: $oddList") // zip - combine two collections val letters = listOf("A", "B", "C", "D", "E") val zipped = numbers.zip(letters) { num, letter -> "$letter$num" } println("Zipped: $zipped") // 5. Custom higher-order functions // withResource pattern (auto-close) fun T.use(block: (T) -> R): R { try { return block(this) } finally { this.close() } } // retry pattern fun retry( times: Int = 3, delay: Long = 100, block: () -> T ): T { var lastException: Throwable? = null for (attempt in 1..times) { try { return block() } catch (e: Exception) { lastException = e println("Attempt $attempt failed: ${e.message}") if (attempt < times) { Thread.sleep(delay) } } } throw lastException ?: RuntimeException("All attempts failed") } // time measurement fun measureTime(block: () -> T): Pair { val start = System.currentTimeMillis() val result = block() val end = System.currentTimeMillis() return Pair(result, end - start) } // Using custom higher-order functions val (result, time) = measureTime { var sum = 0 for (i in 1..1000000) { sum += i } sum } println("Sum 1..1,000,000 = $result (took ${time}ms)") // 6. Inline higher-order functions inline fun List.filterInline( predicate: (T) -> Boolean ): List { val result = mutableListOf() for (element in this) { if (predicate(element)) { result.add(element) } } return result } val largeNumbers = numbers.filterInline { it > 5 } println("Numbers > 5 (inline): $largeNumbers") // 7. Currying implementation // Currying: (A, B) -> C becomes A -> (B -> C) fun curry(f: (A, B) -> C): (A) -> (B) -> C { return { a -> { b -> f(a, b) } } } // Partial application fun partial(f: (A, B) -> C, a: A): (B) -> C { return { b -> f(a, b) } } val add = { a: Int, b: Int -> a + b } val curriedAdd = curry(add) val addFive = curriedAdd(5) println("5 + 3 = ${addFive(3)}") // 8. Function builders and DSLs class Table { private val rows = mutableListOf() fun row(block: Row.() -> Unit) { rows.add(Row().apply(block)) } override fun toString(): String { return "Table:\n${rows.joinToString("\n")}" } } class Row { private val cells = mutableListOf() fun cell(text: String) { cells.add(text) } override fun toString(): String { return " Row: ${cells.joinToString(" | ")}" } } fun table(block: Table.() -> Unit): Table { return Table().apply(block) } val dataTable = table { row { cell("Name") cell("Age") cell("City") } row { cell("Alice") cell("30") cell("NYC") } row { cell("Bob") cell("25") cell("Boston") } } println("\nGenerated Table:") println(dataTable) // 9. Higher-order functions with receivers (DSL) fun buildString(builder: StringBuilder.() -> Unit): String { return StringBuilder().apply(builder).toString() } val complexString = buildString { append("Hello, ") append("World! ") append("The time is ") append(System.currentTimeMillis()) } println("\nBuilt string: $complexString") // 10. Memoization (caching) with higher-order function fun memoize(f: (A) -> R): (A) -> R { val cache = mutableMapOf() return { a -> cache.getOrPut(a) { f(a) } } } // Expensive computation fun fibonacci(n: Int): Int { println("Computing fib($n)") return if (n <= 1) n else fibonacci(n - 1) + fibonacci(n - 2) } val memoizedFib = memoize(::fibonacci) println("\nFibonacci with memoization:") println("fib(5) = ${memoizedFib(5)}") println("fib(5) again = ${memoizedFib(5)}") // Cached println("fib(6) = ${memoizedFib(6)}") // 11. Function pipelines with let, run, with, apply, also data class Person(var name: String, var age: Int) val person = Person("Alice", 25) // let - transform object, return lambda result val letResult = person.let { it.name = it.name.uppercase() it.age * 2 } println("let result: $letResult") // run - like let but with this as receiver val runResult = person.run { name = name.lowercase() age + 10 } println("run result: $runResult") // with - non-extension version of run val withResult = with(person) { name = "Bob" age = 30 "Person updated" } println("with result: $withResult") // apply - configure object, return object itself val appliedPerson = person.apply { name = "Charlie" age = 35 } println("apply result: $appliedPerson") // also - side effects, return object itself val alsoPerson = person.also { println("Before: $it") it.age += 1 println("After: $it") } // 12. Higher-order functions for error handling fun tryOrNull(block: () -> T): T? { return try { block() } catch (e: Exception) { null } } fun parseNumberOrNull(str: String): Int? { return tryOrNull { str.toInt() } } println("\nParsing numbers:") println("parse(\"123\") = ${parseNumberOrNull("123")}") println("parse(\"abc\") = ${parseNumberOrNull("abc")}") // 13. Custom collection operations fun List.filterIndexed(predicate: (Int, T) -> Boolean): List { val result = mutableListOf() forEachIndexed { index, element -> if (predicate(index, element)) { result.add(element) } } return result } fun List.mapIndexedNotNull(transform: (Int, T) -> R?): List { val result = mutableListOf() forEachIndexed { index, element -> transform(index, element)?.let { result.add(it) } } return result } val indexedFilter = numbers.filterIndexed { index, value -> index % 2 == 0 } val indexedMap = numbers.mapIndexedNotNull { index, value -> if (value % 2 == 0) "Even at $index: $value" else null } println("\nCustom operations:") println("Even indices: $indexedFilter") println("Even values with index: $indexedMap") println("\nHigher-order functions demonstration complete!") }

📊 Collections

Collections in Kotlin provide a rich, type-safe API for working with groups of items. Unlike Java, Kotlin distinguishes between mutable and immutable collections at the interface level, encouraging immutability by default. The collections framework is fully interoperable with Java collections while offering extensive extension functions for functional-style operations like mapping, filtering, and reducing.

Immutable vs Mutable Collections

Kotlin's collection interfaces come in pairs: read-only (`List`, `Set`, `Map`) and mutable (`MutableList`, `MutableSet`, `MutableMap`). The read-only interfaces don't guarantee immutability (they might be backed by mutable collections), but they express intent. Factory functions like `listOf()` create read-only lists, while `mutableListOf()` creates mutable ones. This design encourages functional programming patterns.

Collection Factory Functions

Kotlin provides concise factory functions for creating collections: `listOf()`, `mutableListOf()`, `setOf()`, `mutableSetOf()`, `mapOf()`, `mutableMapOf()`. These functions accept varargs or pairs (for maps). Specialized functions like `arrayListOf()`, `hashSetOf()`, and `linkedMapOf()` create specific implementations. Empty collections are created with `emptyList()`, `emptySet()`, `emptyMap()`.

Functional Operations on Collections

The Kotlin standard library provides numerous extension functions for collection processing: `map`, `filter`, `reduce`, `fold`, `flatMap`, `groupBy`, `partition`, `zip`, etc. These higher-order functions enable declarative, functional-style code. Most are inline functions for performance. The operations are lazy for sequences (`asSequence()`) and eager for regular collections.

Sequences for Lazy Evaluation

Sequences (`Sequence`) are Kotlin's equivalent to Java streams, enabling lazy evaluation of collection operations. Unlike regular collections, sequences process elements one-by-one as needed, which can improve performance for large datasets or complex pipelines. Sequences are created with `asSequence()` or `sequenceOf()`. They support the same operations as collections but evaluate lazily.

Collection-Specific Operations

Different collection types have specialized operations: Lists support indexing (`get`, `indexOf`, `subList`), Sets support mathematical operations (`union`, `intersect`, `subtract`), and Maps support key-based access (`getOrPut`, `getOrDefault`). All collections support size checking, emptiness checks, iteration, and conversion to other collection types.

Destructuring on Collections

Lists and arrays support destructuring declarations for accessing first elements: `val (first, second) = list`. However, destructuring only works for the first N elements. For more complex destructuring, use `component1()` to `component5()` functions available on `List`. Pair and Triple are special collection-like types with destructuring support for exactly 2 and 3 elements.

Java Interoperability

Kotlin collections are fully interoperable with Java collections. Kotlin's `List` is seen as `java.util.List` in Java (read-only at compile time but mutable at runtime). When calling Java from Kotlin, collection types become platform types (`List!`). Extension functions like `toList()`, `toMutableList()` convert between Kotlin and Java collection types when needed.

// Collections Examples fun main() { println("=== Collections in Kotlin ===") // 1. Creating collections // Lists val immutableList = listOf("Apple", "Banana", "Cherry") val mutableList = mutableListOf("Dog", "Cat", "Bird") // Sets val immutableSet = setOf(1, 2, 3, 3, 2, 1) // Duplicates removed val mutableSet = mutableSetOf("A", "B", "C") // Maps val immutableMap = mapOf( 1 to "One", 2 to "Two", 3 to "Three" ) val mutableMap = mutableMapOf( "USA" to "Washington D.C.", "France" to "Paris", "Japan" to "Tokyo" ) println("Immutable List: $immutableList") println("Mutable List: $mutableList") println("Immutable Set: $immutableSet") // [1, 2, 3] println("Immutable Map: $immutableMap") // 2. Basic operations // List operations println("\nList Operations:") println("First element: ${immutableList.first()}") println("Last element: ${immutableList.last()}") println("Element at index 1: ${immutableList[1]}") println("Size: ${immutableList.size}") println("Contains 'Banana': ${immutableList.contains("Banana")}") println("Index of 'Cherry': ${immutableList.indexOf("Cherry")}") // Set operations println("\nSet Operations:") println("Contains 2: ${immutableSet.contains(2)}") println("Is empty: ${immutableSet.isEmpty()}") // Map operations println("\nMap Operations:") println("Value for key 2: ${immutableMap[2]}") println("Keys: ${immutableMap.keys}") println("Values: ${immutableMap.values}") println("Entries: ${immutableMap.entries}") println("Contains key 1: ${immutableMap.containsKey(1)}") println("Contains value 'Two': ${immutableMap.containsValue("Two")}") // 3. Mutating mutable collections mutableList.add("Fish") mutableList.add(1, "Rabbit") // Insert at index mutableList.remove("Cat") mutableList[0] = "Puppy" // Update element mutableSet.add("D") mutableSet.remove("B") mutableMap["Germany"] = "Berlin" mutableMap.remove("France") mutableMap["USA"] = "New York" // Update value println("\nAfter mutation:") println("Mutable List: $mutableList") println("Mutable Set: $mutableSet") println("Mutable Map: $mutableMap") // 4. Functional operations val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) println("\nFunctional Operations on numbers: $numbers") // map - transform val squares = numbers.map { it * it } println("Squares: $squares") // filter - select val evens = numbers.filter { it % 2 == 0 } println("Evens: $evens") // reduce - combine val sum = numbers.reduce { acc, num -> acc + num } println("Sum: $sum") // fold - reduce with initial value val product = numbers.fold(1) { acc, num -> acc * num } println("Product: $product") // flatMap - transform and flatten val nested = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6)) val flat = nested.flatMap { it } println("Flat: $flat") // groupBy - group by key val grouped = numbers.groupBy { if (it % 2 == 0) "even" else "odd" } println("Grouped: $grouped") // partition - split val (evenPart, oddPart) = numbers.partition { it % 2 == 0 } println("Even partition: $evenPart") println("Odd partition: $oddPart") // zip - combine two collections val letters = listOf("A", "B", "C", "D", "E") val zipped = numbers.zip(letters) { num, letter -> "$letter$num" } println("Zipped: $zipped") // 5. Set operations val set1 = setOf(1, 2, 3, 4, 5) val set2 = setOf(4, 5, 6, 7, 8) println("\nSet Operations:") println("Set1: $set1, Set2: $set2") println("Union: ${set1 union set2}") println("Intersect: ${set1 intersect set2}") println("Subtract: ${set1 subtract set2}") // 6. Map operations val map1 = mapOf("A" to 1, "B" to 2, "C" to 3) val map2 = mapOf("B" to 20, "C" to 30, "D" to 40) println("\nMap Operations:") println("Map1: $map1, Map2: $map2") println("getOrPut example:") val mutableMap2 = mutableMapOf("A" to 1, "B" to 2) val value = mutableMap2.getOrPut("C") { 3 } // Adds if key doesn't exist println("After getOrPut: $mutableMap2, value: $value") println("getOrDefault: ${map1.getOrDefault("X", 0)}") // 7. Sequences (lazy evaluation) println("\nSequences (lazy evaluation):") val bigList = (1..1_000_000).toList() // Eager evaluation (processes all elements) val eagerStart = System.currentTimeMillis() val eagerResult = bigList .filter { it % 2 == 0 } .map { it * it } .take(10) .toList() val eagerTime = System.currentTimeMillis() - eagerStart // Lazy evaluation with sequence (processes only needed elements) val lazyStart = System.currentTimeMillis() val lazyResult = bigList.asSequence() .filter { // print("f") // Uncomment to see lazy evaluation it % 2 == 0 } .map { // print("m") // Uncomment to see lazy evaluation it * it } .take(10) .toList() val lazyTime = System.currentTimeMillis() - lazyStart println("Eager result: $eagerResult (${eagerTime}ms)") println("Lazy result: $lazyResult (${lazyTime}ms)") // Creating sequences val naturalNumbers = generateSequence(1) { it + 1 } val first10 = naturalNumbers.take(10).toList() println("First 10 natural numbers: $first10") // 8. Collection builders val builtList = buildList { add("First") addAll(listOf("Second", "Third")) if (true) { add("Fourth") } } val builtMap = buildMap { put("A", 1) put("B", 2) putAll(mapOf("C" to 3, "D" to 4)) } println("\nBuilt collections:") println("Built list: $builtList") println("Built map: $builtMap") // 9. Destructuring on collections val listForDestruct = listOf("Alice", "Bob", "Charlie", "Diana") // Destructuring first elements val (first, second) = listForDestruct println("\nDestructuring:") println("First: $first, Second: $second") // Using component functions println("Component1: ${listForDestruct.component1()}") println("Component2: ${listForDestruct.component2()}") // Pair and Triple (special collections) val pair = "key" to "value" // Creates Pair val (key, value2) = pair println("Pair: $pair, key=$key, value=$value2") val triple = Triple(1, "two", 3.0) val (a, b, c) = triple println("Triple: $triple, a=$a, b=$b, c=$c") // 10. Collection transformations println("\nCollection transformations:") // Associate - create map from collection val names = listOf("Alice", "Bob", "Charlie") val nameLengthMap = names.associateWith { it.length } println("Name lengths: $nameLengthMap") val nameIndexMap = names.associateBy { it.first().uppercaseChar() } println("By first letter: $nameIndexMap") // Chunked - split into chunks val chunked = numbers.chunked(3) println("Chunked by 3: $chunked") // Windowed - sliding window val windowed = numbers.windowed(3, step = 2) println("Windowed size 3 step 2: $windowed") // 11. Aggregations println("\nAggregations:") val stats = listOf(10, 20, 30, 40, 50) println("Numbers: $stats") println("Min: ${stats.minOrNull()}") println("Max: ${stats.maxOrNull()}") println("Average: ${stats.average()}") println("Sum: ${stats.sum()}") // 12. Collection predicates println("\nCollection predicates:") println("All even? ${numbers.all { it % 2 == 0 }}") println("Any even? ${numbers.any { it % 2 == 0 }}") println("None negative? ${numbers.none { it < 0 }}") println("Count even: ${numbers.count { it % 2 == 0 }}") // 13. Finding elements println("\nFinding elements:") println("First even: ${numbers.first { it % 2 == 0 }}") println("Last even: ${numbers.last { it % 2 == 0 }}") println("First even or null: ${numbers.firstOrNull { it > 100 }}") // 14. Sorting val unsorted = listOf(5, 3, 8, 1, 9, 2) println("\nSorting:") println("Unsorted: $unsorted") println("Sorted: ${unsorted.sorted()}") println("Sorted descending: ${unsorted.sortedDescending()}") println("Sorted by custom: ${unsorted.sortedBy { -it }}") // 15. Converting between collection types println("\nType conversions:") val setFromList = numbers.toSet() val listFromSet = setFromList.toList() val arrayFromList = numbers.toTypedArray() println("Set from list: $setFromList") println("List from set: $listFromSet") println("Array from list: ${arrayFromList.contentToString()}") // 16. Java interoperability example println("\nJava interoperability:") // Kotlin List is seen as java.util.List in Java // Java List is seen as (Mutable)List! in Kotlin val kotlinList: List = listOf("Kotlin", "Java", "Scala") // In Java, this is java.util.List // When calling Java from Kotlin fun receiveJavaList(list: List) { // Platform type List! val kotlinSafeList = list.toList() // Convert to Kotlin list println("Received from Java: $kotlinSafeList") } println("\nCollections demonstration complete!") }

🌀 Coroutines

Coroutines are Kotlin's solution for asynchronous, non-blocking programming. They provide a way to write asynchronous code sequentially, making it easier to read, write, and reason about compared to callback-based or reactive approaches. Coroutines are lightweight threads—you can launch thousands of them without significant memory overhead. They enable concurrent programming while maintaining the simplicity of synchronous code.

Suspend Functions

The foundation of coroutines is suspend functions—functions that can be paused and resumed later without blocking threads. Suspend functions are marked with the `suspend` modifier and can call other suspend functions. They can only be called from coroutines or other suspend functions. Under the hood, the Kotlin compiler transforms suspend functions into state machines using continuations.

Coroutine Builders

Coroutine builders launch coroutines: `launch` starts a fire-and-forget coroutine (returns `Job`), `async` starts a coroutine that computes a result (returns `Deferred`), and `runBlocking` bridges synchronous and asynchronous code (blocks until completion). Builders accept a coroutine context that defines the dispatcher (thread pool) and other coroutine behavior.

Coroutine Context and Dispatchers

The coroutine context defines the execution environment: `Dispatchers.Default` for CPU-intensive work, `Dispatchers.IO` for I/O operations, `Dispatchers.Main` for UI updates (Android, JavaFX), and `Dispatchers.Unconfined` for no specific thread. Contexts can be combined with `+` operator and include job, exception handler, and name for debugging.

Structured Concurrency

Kotlin promotes structured concurrency—coroutines follow a parent-child hierarchy where children complete before parents. When a parent coroutine is cancelled, all its children are cancelled automatically. This prevents resource leaks and ensures proper cleanup. Coroutine scopes (`CoroutineScope`) manage this hierarchy, with `coroutineScope` and `supervisorScope` providing structured building blocks.

Channels for Communication

Channels provide a way for coroutines to communicate with each other, similar to blocking queues but non-blocking. `Channel` supports multiple producers and consumers with backpressure. Different channel types include: `RendezvousChannel` (no buffer), `LinkedListChannel` (unlimited buffer), `ArrayChannel` (fixed buffer), and `ConflatedChannel` (keeps only latest).

Flows for Reactive Streams

Flows are Kotlin's reactive streams implementation for asynchronous data streams that can emit multiple values over time. They're cold streams—data is produced on demand. Flows support operators like `map`, `filter`, `transform`, and can be transformed to/from other reactive types. They integrate with coroutines and support backpressure automatically.

Error Handling and Supervision

Coroutines provide robust error handling: exceptions propagate up the coroutine hierarchy. `CoroutineExceptionHandler` catches unhandled exceptions. `SupervisorJob` prevents child failures from cancelling siblings. `async` wraps exceptions in `Deferred` results. `try/catch` works normally within coroutines. Proper error handling is crucial for production coroutine code.

// Coroutines Examples import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.* import java.util.concurrent.atomic.AtomicInteger import kotlin.system.measureTimeMillis fun main() = runBlocking { println("=== Coroutines in Kotlin ===") // 1. Basic coroutine launch println("\n1. Basic coroutine launch:") launch { // This runs in a coroutine delay(1000) // Non-blocking delay println("Hello from coroutine!") } println("Hello from main") // Wait for coroutine to complete (in real example) delay(1500) // 2. Suspend functions println("\n2. Suspend functions:") suspend fun fetchUser(id: Int): String { delay(500) // Simulate network call return "User$id" } suspend fun fetchPosts(userId: Int): List { delay(300) // Simulate database query return listOf("Post1", "Post2", "Post3") } // Calling suspend functions val user = fetchUser(1) val posts = fetchPosts(1) println("User: $user, Posts: $posts") // 3. Async for concurrent operations println("\n3. Async for concurrency:") val time = measureTimeMillis { val userDeferred = async { fetchUser(2) } val postsDeferred = async { fetchPosts(2) } // Both fetches run concurrently val user2 = userDeferred.await() val posts2 = postsDeferred.await() println("Concurrent result: User: $user2, Posts: $posts2") } println("Time taken: ${time}ms (should be ~500ms, not 800ms)") // 4. Coroutine contexts and dispatchers println("\n4. Coroutine contexts and dispatchers:") launch(Dispatchers.Default) { println("Running on Default dispatcher - for CPU-intensive work") } launch(Dispatchers.IO) { println("Running on IO dispatcher - for I/O operations") } launch(Dispatchers.Unconfined) { println("Running on Unconfined - not confined to any thread") } // Custom context val customContext = Dispatchers.Default + CoroutineName("MyCoroutine") launch(customContext) { println("Running with custom context: ${coroutineContext[CoroutineName]}") } delay(100) // Let coroutines complete // 5. Structured concurrency println("\n5. Structured concurrency:") suspend fun fetchUserAndPosts(userId: Int): Pair> = coroutineScope { val userDeferred = async { fetchUser(userId) } val postsDeferred = async { fetchPosts(userId) } userDeferred.await() to postsDeferred.await() } val (user3, posts3) = fetchUserAndPosts(3) println("Structured fetch: User: $user3, Posts: $posts3") // 6. Error handling println("\n6. Error handling:") val handler = CoroutineExceptionHandler { _, exception -> println("Caught exception: $exception") } val job = GlobalScope.launch(handler) { throw RuntimeException("Test exception") } job.join() // Wait for completion // Try/catch in coroutines launch { try { delay(100) throw RuntimeException("Error in coroutine") } catch (e: Exception) { println("Caught: ${e.message}") } } delay(200) // 7. Channels for communication println("\n7. Channels for communication:") val channel = Channel() launch { // Producer for (x in 1..5) { channel.send(x * x) delay(100) } channel.close() // Close when done } launch { // Consumer for (value in channel) { println("Received: $value") } println("Channel closed") } delay(1000) // 8. Flows (reactive streams) println("\n8. Flows (reactive streams):") fun simpleFlow(): Flow = flow { // Flow builder for (i in 1..3) { delay(100) // Simulate work emit(i) // Emit value } } launch { simpleFlow() .map { it * it } // Transform .filter { it % 2 != 0 } // Filter .collect { value -> // Terminal operator println("Flow value: $value") } } delay(500) // 9. StateFlow and SharedFlow println("\n9. StateFlow and SharedFlow:") val stateFlow = MutableStateFlow(0) // Collector 1 launch { stateFlow.collect { value -> println("Collector 1: $value") } } // Collector 2 launch { stateFlow.collect { value -> println("Collector 2: $value") } } // Update state stateFlow.value = 1 delay(100) stateFlow.value = 2 delay(100) // 10. Coroutine cancellation println("\n10. Coroutine cancellation:") val jobToCancel = launch { try { repeat(1000) { i -> println("Job: I'm sleeping $i ...") delay(500) // Check for cancellation ensureActive() } } catch (e: CancellationException) { println("Job was cancelled") throw e } finally { println("Job: Finally block (cleanup)") } } delay(1500) // Let it run a bit println("Main: I'm tired of waiting!") jobToCancel.cancelAndJoin() // Cancel and wait for completion println("Main: Now I can quit.") // 11. Supervisor scope println("\n11. Supervisor scope:") supervisorScope { val child1 = launch { try { delay(Long.MAX_VALUE) } finally { println("Child 1 cancelled") } } val child2 = launch { delay(100) throw RuntimeException("Child 2 failed") } delay(200) println("Child 1 is active: ${child1.isActive}") println("Child 2 is active: ${child2.isActive}") } // 12. Select expression println("\n12. Select expression (experimental):") val channelA = Channel() val channelB = Channel() launch { channelA.send("Message from A") delay(200) channelB.send("Message from B") } launch { // Select the first available channel select { channelA.onReceive { value -> println("Received from A: $value") } channelB.onReceive { value -> println("Received from B: $value") } } } delay(500) // 13. Flow operators println("\n13. Flow operators:") val numberFlow = flow { for (i in 1..5) { delay(50) emit(i) } } launch { numberFlow .transform { value -> // Transform each value if (value % 2 == 0) { emit("Even: $value") } else { emit("Odd: $value") } } .onEach { value -> // Side effect println("Processing: $value") } .collect { value -> println("Collected: $value") } } delay(400) // 14. Flow exception handling println("\n14. Flow exception handling:") val errorFlow = flow { for (i in 1..3) { if (i == 2) { throw RuntimeException("Error on $i") } emit(i) } } launch { errorFlow .catch { e -> // Handle exception in flow println("Caught in flow: ${e.message}") emit(-1) // Emit recovery value } .collect { value -> println("Error flow value: $value") } } delay(300) // 15. Real-world example: parallel processing println("\n15. Real-world example:") suspend fun processItem(item: Int): String { delay(100) // Simulate processing return "Processed $item" } val items = (1..10).toList() val processingTime = measureTimeMillis { val results = items.map { item -> async { processItem(item) } }.awaitAll() println("Processed ${results.size} items: ${results.take(3)}...") } println("Processing time: ${processingTime}ms (parallel)") val sequentialTime = measureTimeMillis { val results = mutableListOf() for (item in items) { results.add(processItem(item)) } println("Sequential processing time") } println("Sequential time: ${sequentialTime}ms") println("\nCoroutines demonstration complete!") // Cleanup coroutineContext.cancelChildren() }

⏳ Async Programming

Asynchronous Programming in Kotlin is built around coroutines, providing a simpler alternative to callback-based or reactive approaches. Async programming allows non-blocking execution, meaning your program can perform other work while waiting for long-running operations like network requests, file I/O, or database queries. Kotlin's approach makes async code look like synchronous code, dramatically improving readability and maintainability.

From Callbacks to Coroutines

Traditional async programming uses callbacks, leading to "callback hell" with deeply nested code. Kotlin coroutines replace callbacks with sequential-looking code using `suspend` functions. The compiler transforms suspend functions into callback-based state machines, but developers write linear code. This eliminates nesting and makes error handling straightforward with regular `try/catch`.

Structured Concurrency Principles

Kotlin's async programming follows structured concurrency principles: coroutines have parent-child relationships, and children complete before their parent. When a parent is cancelled, all children are cancelled. Resources are automatically cleaned up. This structure prevents common async bugs like leaked coroutines or orphaned operations. Scopes (`CoroutineScope`) manage these relationships.

Async/Await Pattern

The `async`/`await` pattern (via `async` builder and `await()` method) allows concurrent execution of independent tasks. `async` starts a coroutine that returns a `Deferred` (a future/promise). `await()` suspends until the result is ready. Multiple `async` calls can run in parallel, and `await()` collects results. This pattern is ideal for parallelizing independent operations.

Non-Blocking vs Blocking

Coroutines provide non-blocking suspension: when a coroutine waits (with `delay()` or `await()`), it releases its thread for other work. This enables high concurrency with few threads. Contrast with blocking operations that hold a thread idle. Kotlin provides `withContext()` to switch dispatchers for blocking operations, keeping the rest of the code non-blocking.

Error Propagation in Async Code

In async programming, errors need special handling. Kotlin provides several approaches: `try/catch` around `await()`, `CoroutineExceptionHandler` for unhandled exceptions, `supervisorScope` for isolated failures, and wrapping results in `Result` or sealed classes. Proper error handling prevents silent failures and ensures robustness in production systems.

Testing Async Code

Testing async code requires special consideration. Kotlin provides `TestCoroutineDispatcher` and `runBlockingTest` for deterministic testing. `runBlockingTest` provides virtual time control, allowing fast-forwarding through delays. Test dispatchers enable controlling coroutine execution in tests. Proper testing ensures async code behaves correctly under various conditions.

Integration with Existing APIs

Kotlin provides ways to integrate coroutines with callback-based APIs: `suspendCancellableCoroutine` and `callbackFlow` bridge callbacks to coroutines. `CompletableFuture` from Java can be converted to coroutines with `asDeferred()`. RxJava observables can interoperate with flows. These integrations allow gradual migration to coroutines.

// Async Programming Examples import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlin.system.measureTimeMillis fun main() = runBlocking { println("=== Async Programming in Kotlin ===") // 1. Basic async/await pattern println("\n1. Async/Await pattern:") suspend fun fetchDataFromSource1(): String { delay(1000) // Simulate network call return "Data from source 1" } suspend fun fetchDataFromSource2(): String { delay(800) // Simulate network call return "Data from source 2" } suspend fun fetchDataFromSource3(): String { delay(600) // Simulate network call return "Data from source 3" } val time = measureTimeMillis { // Launch async operations concurrently val deferred1 = async { fetchDataFromSource1() } val deferred2 = async { fetchDataFromSource2() } val deferred3 = async { fetchDataFromSource3() } // Await all results (runs concurrently) val result1 = deferred1.await() val result2 = deferred2.await() val result3 = deferred3.await() println("Results: $result1, $result2, $result3") } println("Total time: ${time}ms (should be ~1000ms, not 2400ms)") // 2. Converting callback-based APIs to coroutines println("\n2. Converting callbacks to coroutines:") // Simulating a callback-based API class CallbackApi { fun fetchData(callback: (String) -> Unit) { Thread { Thread.sleep(500) // Simulate work callback("Data from callback") }.start() } fun fetchDataWithError(callback: (String?, Throwable?) -> Unit) { Thread { Thread.sleep(300) if (Math.random() > 0.5) { callback("Success", null) } else { callback(null, RuntimeException("API error")) } }.start() } } // Wrap callback API in suspend function suspend fun CallbackApi.fetchDataSuspend(): String = suspendCancellableCoroutine { continuation -> fetchData { result -> continuation.resume(result) } // Handle cancellation continuation.invokeOnCancellation { println("Fetch cancelled") } } suspend fun CallbackApi.fetchDataWithErrorSuspend(): String = suspendCancellableCoroutine { continuation -> fetchDataWithError { result, error -> if (error != null) { continuation.resumeWithException(error) } else { continuation.resume(result!!) } } } val api = CallbackApi() val data = api.fetchDataSuspend() println("Data from callback API: $data") try { val dataWithError = api.fetchDataWithErrorSuspend() println("Data with possible error: $dataWithError") } catch (e: Exception) { println("Caught error: ${e.message}") } // 3. Structured concurrency with async println("\n3. Structured concurrency:") suspend fun fetchUserProfile(userId: Int): Map = coroutineScope { val userDeferred = async { delay(200) mapOf("id" to userId, "name" to "User$userId") } val postsDeferred = async { delay(300) listOf("Post1", "Post2", "Post3") } val friendsDeferred = async { delay(150) listOf("Friend1", "Friend2") } mapOf( "user" to userDeferred.await(), "posts" to postsDeferred.await(), "friends" to friendsDeferred.await() ) } val profile = fetchUserProfile(1) println("User profile: $profile") // 4. Error handling in async operations println("\n4. Error handling:") suspend fun riskyOperation(id: Int): String { delay(100) if (id == 2) { throw RuntimeException("Failed for ID $id") } return "Result for $id" } // Approach 1: try/catch around each async val job1 = launch { try { val result = async { riskyOperation(1) }.await() println("Result 1: $result") } catch (e: Exception) { println("Caught error for op 1: ${e.message}") } } // Approach 2: Handle errors in Deferred val deferred2 = async { try { riskyOperation(2) } catch (e: Exception) { "Error: ${e.message}" } } val deferred3 = async { riskyOperation(3) } job1.join() println("Result 2: ${deferred2.await()}") println("Result 3: ${deferred3.await()}") // 5. Timeouts and cancellation println("\n5. Timeouts and cancellation:") suspend fun longRunningTask(): String { try { delay(5000) // Long operation return "Completed" } catch (e: CancellationException) { println("Task was cancelled") throw e } } // With timeout try { val result = withTimeout(1000) { longRunningTask() } println("Result: $result") } catch (e: TimeoutCancellationException) { println("Task timed out: ${e.message}") } // With timeoutOrNull val result = withTimeoutOrNull(1000) { longRunningTask() } println("Result or null: $result") // 6. Parallel processing with async println("\n6. Parallel processing:") suspend fun processItem(item: Int): String { delay(100) // Simulate processing return "Processed $item" } val items = (1..10).toList() // Sequential processing val sequentialTime = measureTimeMillis { val sequentialResults = mutableListOf() for (item in items) { sequentialResults.add(processItem(item)) } println("Sequential: processed ${sequentialResults.size} items") } // Parallel processing val parallelTime = measureTimeMillis { val deferredResults = items.map { item -> async { processItem(item) } } val parallelResults = deferredResults.awaitAll() println("Parallel: processed ${parallelResults.size} items") } println("Sequential time: ${sequentialTime}ms") println("Parallel time: ${parallelTime}ms") println("Speedup: ${sequentialTime.toDouble() / parallelTime}x") // 7. Resource management with async println("\n7. Resource management:") class DatabaseConnection : AutoCloseable { init { println("Database connected") } fun query() = "Query result" override fun close() { println("Database disconnected") } } suspend fun withDatabase(block: suspend (DatabaseConnection) -> String): String { val connection = DatabaseConnection() return try { block(connection) } finally { connection.close() } } val dbResult = withDatabase { connection -> delay(100) // Simulate query connection.query() } println("Database result: $dbResult") // 8. Combining multiple async patterns println("\n8. Combining async patterns:") data class User(val id: Int, val name: String) data class Post(val id: Int, val title: String, val userId: Int) data class Comment(val id: Int, val text: String, val postId: Int) suspend fun fetchUsers(): List { delay(200) return listOf(User(1, "Alice"), User(2, "Bob")) } suspend fun fetchPosts(userId: Int): List { delay(150) return listOf(Post(1, "Post 1 by $userId", userId), Post(2, "Post 2 by $userId", userId)) } suspend fun fetchComments(postId: Int): List { delay(100) return listOf(Comment(1, "Great post!", postId), Comment(2, "Thanks!", postId)) } suspend fun fetchCompleteData(): List, List>>> = coroutineScope { val users = fetchUsers() users.map { user -> async { val posts = fetchPosts(user.id) val postComments = posts.map { post -> async { fetchComments(post.id) } }.awaitAll() Triple(user, posts, postComments) } }.awaitAll() } val completeData = fetchCompleteData() println("Complete data fetched for ${completeData.size} users") // 9. Rate limiting and batching println("\n9. Rate limiting and batching:") suspend fun callApi(item: Int): String { delay(50) // Simulate API call return "API result for $item" } // Process with concurrency limit val batchSize = 3 val batches = items.chunked(batchSize) val batchedTime = measureTimeMillis { val allResults = mutableListOf() for (batch in batches) { val batchResults = batch.map { item -> async { callApi(item) } }.awaitAll() allResults.addAll(batchResults) delay(100) // Rate limiting between batches } println("Batched: processed ${allResults.size} items in ${batches.size} batches") } println("Batched time: ${batchedTime}ms") // 10. Async with flows println("\n10. Async with flows:") fun pollForUpdates(): Flow = flow { var counter = 0 while (true) { delay(1000) // Poll every second emit(counter++) } } val updateJob = launch { pollForUpdates() .take(5) // Take 5 updates .collect { update -> println("Update received: $update") } } updateJob.join() // 11. Real-world example: concurrent web scraping println("\n11. Real-world example: concurrent web scraping") suspend fun scrapePage(url: String): List { delay((100..500).random().toLong()) // Simulate varying load times return listOf("Data from $url") } val urls = listOf( "https://example.com/page1", "https://example.com/page2", "https://example.com/page3", "https://example.com/page4", "https://example.com/page5" ) val scrapeTime = measureTimeMillis { val scrapedData = urls.map { url -> async { scrapePage(url) } }.awaitAll() .flatten() println("Scraped ${scrapedData.size} items from ${urls.size} URLs") } println("Scraping time: ${scrapeTime}ms") // 12. Testing async code (conceptual) println("\n12. Testing async code (conceptual):") // In tests, you would use: // runBlockingTest { // // Test code with virtual time control // val deferred = async { fetchData() } // advanceTimeBy(1000) // Fast-forward // assertEquals("Expected", deferred.await()) // } println("\nAsync programming demonstration complete!") }

🌊 Flows

Flows are Kotlin's reactive streams implementation for asynchronous data streams that can emit multiple values over time. They represent cold asynchronous data streams—data is produced on demand when collectors are present. Flows are built on coroutines and provide a declarative way to handle streams of values with backpressure support, making them ideal for live data, event streams, or any scenario where data arrives over time.

Cold vs Hot Streams

Flows are cold streams: they start producing values only when a collector starts collecting. Each collector gets its own independent stream. This differs from hot streams (like `StateFlow` or `SharedFlow`) that emit values regardless of collectors. Cold flows are suitable for one-to-one producer-consumer relationships, while hot flows are better for broadcasting to multiple consumers.

Flow Builders

Several ways create flows: `flow { ... }` builder for custom emission, `flowOf()` for fixed values, `asFlow()` extension to convert collections/channels, `channelFlow { ... }` for complex producers with await/emit, and `callbackFlow { ... }` to bridge callback APIs. Each builder serves different use cases from simple to complex stream creation.

Flow Operators

Flows support rich operators similar to collections: `map`, `filter`, `transform`, `take`, `drop`, `zip`, `combine`, etc. Operators are inline extension functions that return new flows. They can be intermediate (return flow) or terminal (start collection). Operators are sequential by default but can be made concurrent with `flowOn` or `buffer`.

Exception Handling

Flow exceptions can be caught with `catch` operator, which can emit recovery values or rethrow. The `retry` operator retries collection on failure. Terminal operators like `collect` can wrap collection in `try/catch`. For transparent error handling, flows can emit `Result` or sealed class wrappers. Proper error handling ensures robust stream processing.

Context and Threading

Flows are sequential and execute in the context of the collector by default. `flowOn` changes the context for upstream operations. `buffer` allows concurrent processing by buffering emissions. `conflate` drops intermediate values when consumer is slow. `collectLatest` cancels slow collection when new value arrives. These operators control flow execution and backpressure.

StateFlow and SharedFlow

`StateFlow` is a hot flow that represents a state holder with a single current value and replays it to new collectors. `SharedFlow` is a hot flow that broadcasts values to multiple collectors with configurable replay cache. Both are used for state management and event broadcasting in MVVM/MVI architectures, particularly in Android apps.

Integration with Other APIs

Flows integrate with other reactive APIs: they can be converted to/from RxJava Observables/Flowables with `asFlow()`/`asObservable()`. Channels can be converted to flows with `receiveAsFlow()`. Callback APIs can be adapted with `callbackFlow`. LiveData (Android) can interop with `asLiveData()`. This enables gradual adoption and interoperability.

// Flows Examples import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlin.system.measureTimeMillis fun main() = runBlocking { println("=== Flows in Kotlin ===") // 1. Basic flow creation println("\n1. Basic flow creation:") // flow builder fun simpleFlow(): Flow = flow { println("Flow started") for (i in 1..3) { delay(100) // Simulate work emit(i) // Emit value } } // Collect the flow (terminal operator) simpleFlow().collect { value -> println("Collected: $value") } // flowOf for fixed values val fixedFlow = flowOf(1, 2, 3, 4, 5) // asFlow from collections val listFlow = listOf("A", "B", "C").asFlow() // 2. Flow operators (intermediate) println("\n2. Flow operators:") val numbersFlow = (1..10).asFlow() numbersFlow .filter { it % 2 == 0 } // Keep even numbers .map { it * it } // Square them .transform { value -> // Custom transformation if (value > 20) { emit("Large: $value") } else { emit("Small: $value") } } .collect { value -> println("Transformed: $value") } // 3. Terminal operators println("\n3. Terminal operators:") val sumFlow = (1..5).asFlow() // toList/toSet - collect to collection val list = sumFlow.toList() println("ToList: $list") // single/singleOrNull - expect single value val singleFlow = flowOf(42) val singleValue = singleFlow.single() println("Single: $singleValue") // reduce/fold - combine values val reduceFlow = (1..5).asFlow() val sum = reduceFlow.reduce { acc, value -> acc + value } println("Reduce sum: $sum") val foldFlow = (1..5).asFlow() val product = foldFlow.fold(1) { acc, value -> acc * value } println("Fold product: $product") // first/last - get first/last value val firstFlow = (1..10).asFlow() val firstEven = firstFlow.first { it % 2 == 0 } println("First even: $firstEven") // count - count elements val countFlow = (1..10).asFlow() val count = countFlow.count { it > 5 } println("Count > 5: $count") // 4. Flow context and threading println("\n4. Flow context and threading:") fun contextFlow(): Flow = flow { for (i in 1..3) { Thread.sleep(100) // Simulate CPU-intensive work emit(i) println("Emitted $i from ${Thread.currentThread().name}") } } // flowOn changes context for upstream contextFlow() .flowOn(Dispatchers.Default) // Run emitter in background .collect { value -> println("Collected $value in ${Thread.currentThread().name}") } // 5. Buffering and concurrency println("\n5. Buffering and concurrency:") fun slowFlow(): Flow = flow { for (i in 1..3) { delay(100) // Slow producer emit(i) println("Emitted $i at ${System.currentTimeMillis()}") } } val time = measureTimeMillis { slowFlow() .buffer() // Buffer emissions, don't wait .collect { value -> delay(300) // Slow consumer println("Collected $value at ${System.currentTimeMillis()}") } } println("Buffered collection took ${time}ms") // conflate - skip intermediate values val conflateTime = measureTimeMillis { slowFlow() .conflate() // Keep only latest .collect { value -> delay(300) println("Conflated $value") } } println("Conflated collection took ${conflateTime}ms") // collectLatest - cancel slow collection on new value val latestTime = measureTimeMillis { slowFlow() .collectLatest { value -> // Cancel previous collection println("Collecting $value") delay(300) // Will be cancelled by next emission println("Collected $value") // May not reach here } } println("collectLatest took ${latestTime}ms") // 6. Combining flows println("\n6. Combining flows:") val flowA = (1..3).asFlow().onEach { delay(100) } val flowB = flowOf("A", "B", "C").onEach { delay(150) } // zip - combine corresponding elements flowA.zip(flowB) { a, b -> "$a -> $b" } .collect { println("Zipped: $it") } // combine - combine latest values val combined = flowA.combine(flowB) { a, b -> "$a - $b" } println("Combined sample: ") combined.take(5).collect { println(" $it") } // merge - merge emissions from multiple flows val merged = merge(flowA.map { "Number: $it" }, flowB.map { "Letter: $it" }) println("Merged sample: ") merged.take(6).collect { println(" $it") } // 7. Exception handling in flows println("\n7. Exception handling:") fun failingFlow(): Flow = flow { for (i in 1..3) { delay(100) if (i == 2) { throw RuntimeException("Error on $i") } emit(i) } } // catch operator failingFlow() .catch { e -> println("Caught: ${e.message}") emit(-1) // Emit recovery value } .collect { value -> println("Value with catch: $value") } // retry operator var attempt = 0 fun retryFlow(): Flow = flow { attempt++ println("Attempt $attempt") if (attempt < 3) { throw RuntimeException("Fail attempt $attempt") } emit(42) } retryFlow() .retry(2) { cause -> println("Retrying after: ${cause.message}") delay(100) true // Retry } .collect { value -> println("After retry: $value") } // 8. StateFlow (hot flow with state) println("\n8. StateFlow:") val stateFlow = MutableStateFlow(0) // Initial value // Collector 1 val job1 = launch { stateFlow.collect { value -> println("Collector 1: $value") } } // Collector 2 (starts later) delay(50) val job2 = launch { stateFlow.collect { value -> println("Collector 2: $value") } } // Update state stateFlow.value = 1 delay(100) stateFlow.value = 2 delay(100) job1.cancel() job2.cancel() // 9. SharedFlow (hot flow for events) println("\n9. SharedFlow:") val sharedFlow = MutableSharedFlow( replay = 2, // Replay last 2 values to new collectors extraBufferCapacity = 10 ) // Emitter launch { for (i in 1..5) { delay(100) sharedFlow.emit(i) println("Emitted: $i") } } // Collector (starts after some emissions) delay(250) launch { sharedFlow.collect { value -> println("Late collector: $value") } } delay(500) // Let it run // 10. Channel flow (complex producers) println("\n10. Channel flow:") fun channelFlowExample(): Flow = channelFlow { // Can use coroutine builders inside launch { for (i in 1..3) { delay(100) send(i * 2) } } launch { for (i in 1..3) { delay(150) send(i * 3) } } } channelFlowExample() .take(6) .collect { value -> println("Channel flow: $value") } // 11. Callback to flow println("\n11. Callback to flow:") class EventEmitter { private val listeners = mutableListOf<(String) -> Unit>() fun emitEvent(event: String) { listeners.forEach { it(event) } } fun events(): Flow = callbackFlow { val listener = { event: String -> trySend(event) } listeners.add(listener) awaitClose { listeners.remove(listener) println("Listener removed") } } } val emitter = EventEmitter() val eventJob = launch { emitter.events() .take(3) .collect { event -> println("Event: $event") } } // Emit events emitter.emitEvent("Event 1") delay(100) emitter.emitEvent("Event 2") delay(100) emitter.emitEvent("Event 3") eventJob.join() // 12. Real-world example: paginated API println("\n12. Real-world example: paginated API") data class Page(val items: List, val hasNext: Boolean) fun fetchPage(page: Int): Page { // Simulate API call val items = (1..10).map { "Item ${(page - 1) * 10 + it}" } return Page(items, page < 3) // 3 pages total } fun paginatedFlow(): Flow = flow { var page = 1 var hasNext = true while (hasNext) { println("Fetching page $page") val result = fetchPage(page) result.items.forEach { item -> emit(item) } hasNext = result.hasNext page++ delay(200) // Rate limiting } } paginatedFlow() .take(15) // Take first 15 items .collect { item -> println(" $item") } // 13. Flow transformations println("\n13. Flow transformations:") fun tickerFlow(period: Long): Flow = flow { while (true) { emit(Unit) delay(period) } } // Transform ticker into sequential numbers tickerFlow(100) .scan(0) { acc, _ -> acc + 1 } // Like fold but emits intermediate values .take(5) .collect { count -> println("Ticker count: $count") } // 14. Flow with timeout println("\n14. Flow with timeout:") fun slowEmitFlow(): Flow = flow { for (i in 1..3) { delay(200) // Slow emission emit(i) } } // Timeout on each emission try { slowEmitFlow() .onEach { value -> withTimeout(150) { // Timeout per element delay(100) // Fast processing println("Processed $value in time") } } .collect() } catch (e: TimeoutCancellationException) { println("Timeout: ${e.message}") } println("\nFlows demonstration complete!") }

📡 Channels

Channels in Kotlin coroutines provide a way for coroutines to communicate with each other by sending and receiving elements. They are conceptually similar to blocking queues but are non-blocking and suspending. Channels support multiple producers and consumers and are a fundamental building block for implementing producer-consumer patterns, work pools, and event broadcasting in concurrent applications.

Channel Basics and Types

Channels come in several types with different buffering strategies: `RendezvousChannel` (no buffer, sender suspends until receiver ready), `LinkedListChannel` (unlimited buffer), `ArrayChannel` (fixed-size buffer), and `ConflatedChannel` (buffer of 1, new value replaces old). The default `Channel()` creates a rendezvous channel. Buffering affects backpressure handling and performance characteristics.

Channel Operations

Basic channel operations are `send(element)` to send (suspends if buffer full) and `receive()` to receive (suspends if buffer empty). `offer(element)` tries to send without suspension (returns success/failure). `poll()` tries to receive without suspension. `close()` closes the channel (sending `ClosedSendChannelException` to new senders). `isClosedForSend` and `isClosedForReceive` check channel state.

Producer-Consumer Patterns

Channels naturally implement producer-consumer patterns. `produce` builder creates a coroutine that produces values to a channel. `consumeEach` extension consumes all values from a channel. Multiple producers can send to the same channel, and multiple consumers can receive, though each element is delivered to only one consumer. This enables work distribution patterns.

Fan-out and Fan-in

Fan-out: multiple coroutines receive from the same channel (competing consumers). Each element goes to only one consumer. Fan-in: multiple coroutines send to the same channel. Channels handle synchronization automatically. These patterns enable scaling consumer/producer sides independently. For broadcasting to multiple consumers, use `BroadcastChannel` or `SharedFlow`.

Channel Pipelines

Channels can be connected in pipelines: one coroutine receives from a channel, processes, and sends to another channel. This enables staged processing with backpressure propagation. Pipelines can be linear or form directed acyclic graphs. Each stage can run on different dispatchers for parallelism. Pipelines are a powerful pattern for data processing.

Select Expression with Channels

The `select` expression (experimental) allows waiting for the first completed operation among multiple channels. It can `onReceive` from channels, `onSend` to channels, `onAwait` on `Deferred`, or `onTimeout`. Select enables building reactive systems that respond to the first available event. It's useful for implementing timeouts, priority channels, or multiplexing.

Channels vs Flows

Channels are hot: they exist independently of consumers and buffer elements. Flows are cold: they produce elements on demand. Channels are for communication between coroutines. Flows are for streaming data with transformations. Use channels when you need decoupled producers/consumers with buffering. Use flows for data transformations and cold streams.

// Channels Examples import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlin.system.measureTimeMillis fun main() = runBlocking { println("=== Channels in Kotlin ===") // 1. Basic channel operations println("\n1. Basic channel operations:") val channel = Channel() launch { // Producer for (x in 1..5) { delay(100) println("Sending $x") channel.send(x * x) } channel.close() // Close when done println("Channel closed") } launch { // Consumer for (y in channel) { println("Received $y") delay(200) // Slow consumer } } delay(1500) // 2. Different channel types println("\n2. Different channel types:") // Rendezvous channel (default) - no buffer val rendezvousChannel = Channel() // Buffered channel - capacity 2 val bufferedChannel = Channel(capacity = 2) // Conflated channel - keep only latest val conflatedChannel = Channel(Channel.CONFLATED) // Unlimited channel val unlimitedChannel = Channel(Channel.UNLIMITED) // Test buffered channel launch { for (i in 1..5) { bufferedChannel.send("Message $i") println("Sent $i to buffered channel") delay(50) } bufferedChannel.close() } delay(100) // Let sender fill buffer launch { for (msg in bufferedChannel) { println("Buffered received: $msg") delay(150) // Slow receiver } } delay(1000) // 3. Producer and consumer builders println("\n3. Producer and consumer builders:") // produce builder val numberChannel = produce { for (x in 1..5) { send(x) delay(100) } } // consumeEach extension numberChannel.consumeEach { value -> println("Produced: $value") } // 4. Multiple producers println("\n4. Multiple producers:") val multiChannel = Channel() // Producer 1 launch { for (i in 1..3) { delay(120) multiChannel.send("Producer 1: $i") } } // Producer 2 launch { for (i in 1..3) { delay(80) multiChannel.send("Producer 2: $i") } } // Consumer launch { delay(500) // Start consuming after some production for (msg in multiChannel) { println("Consumed: $msg") } } delay(1000) multiChannel.close() // 5. Multiple consumers (fan-out) println("\n5. Multiple consumers (fan-out):") val fanOutChannel = Channel() // Producer launch { for (x in 1..10) { fanOutChannel.send(x) delay(50) } fanOutChannel.close() } // Consumer 1 launch { for (value in fanOutChannel) { println("Consumer 1: $value") delay(100) } } // Consumer 2 launch { for (value in fanOutChannel) { println("Consumer 2: $value") delay(150) } } delay(1500) // 6. Pipeline pattern println("\n6. Pipeline pattern:") // Stage 1: Generate numbers fun CoroutineScope.numbersFrom(start: Int) = produce { var x = start while (true) { send(x++) delay(100) } } // Stage 2: Filter primes fun CoroutineScope.filterPrimes(numbers: ReceiveChannel) = produce { for (x in numbers) { if (isPrime(x)) { send(x) } } } // Simple prime check fun isPrime(n: Int): Boolean { if (n <= 1) return false for (i in 2..n/2) { if (n % i == 0) return false } return true } // Build pipeline val numbers = numbersFrom(2) val primes = filterPrimes(numbers) // Consume first 10 primes repeat(10) { println("Prime: ${primes.receive()}") } coroutineContext.cancelChildren() // Cancel pipeline // 7. Fan-in pattern println("\n7. Fan-in pattern:") suspend fun sendString(channel: SendChannel, text: String, time: Long) { while (true) { delay(time) channel.send(text) } } val fanInChannel = Channel() launch { sendString(fanInChannel, "Foo", 200L) } launch { sendString(fanInChannel, "BAR", 300L) } // Receive first 6 repeat(6) { println(fanInChannel.receive()) } coroutineContext.cancelChildren() // 8. Select expression with channels println("\n8. Select expression (experimental):") val channelA = Channel() val channelB = Channel() launch { delay(100) channelA.send("Channel A") delay(200) channelB.send("Channel B") delay(100) channelA.send("Channel A again") } launch { repeat(3) { select { channelA.onReceive { value -> println("Selected from A: $value") } channelB.onReceive { value -> println("Selected from B: $value") } } } } delay(500) // 9. Channel timeouts with select println("\n9. Channel timeouts:") val timeoutChannel = Channel() launch { delay(250) timeoutChannel.send("Data arrived") } select { timeoutChannel.onReceive { value -> println("Received: $value") } onTimeout(200) { println("Timed out waiting for channel") } } // 10. Broadcast channel (for multiple consumers) println("\n10. Broadcast channel:") val broadcastChannel = BroadcastChannel(1) // Consumer 1 val receiver1 = broadcastChannel.openSubscription() launch { for (value in receiver1) { println("Broadcast receiver 1: $value") } } // Consumer 2 val receiver2 = broadcastChannel.openSubscription() launch { delay(100) // Start later for (value in receiver2) { println("Broadcast receiver 2: $value") } } // Producer launch { for (i in 1..3) { delay(150) broadcastChannel.send(i) } broadcastChannel.close() } delay(1000) // 11. Work pool pattern println("\n11. Work pool pattern:") data class WorkItem(val id: Int, val data: String) val workChannel = Channel() val resultChannel = Channel() // Workers repeat(3) { workerId -> launch { for (work in workChannel) { delay((100..300).random().toLong()) // Simulate work val result = "Worker $workerId processed ${work.id}: ${work.data.uppercase()}" resultChannel.send(result) } } } // Producer launch { for (i in 1..10) { workChannel.send(WorkItem(i, "item-$i")) } workChannel.close() } // Result collector launch { var count = 0 for (result in resultChannel) { println("Result: $result") if (++count == 10) { resultChannel.close() } } } delay(2000) // 12. Rate limiting with channels println("\n12. Rate limiting:") val rateLimitChannel = Channel(Channel.RENDEZVOUS) // Rate limiter launch { for (item in rateLimitChannel) { println("Processing: $item at ${System.currentTimeMillis()}") delay(200) // Process at most 5 items per second } } // Fast producer launch { for (i in 1..10) { rateLimitChannel.send(i) println("Sent: $i at ${System.currentTimeMillis()}") } rateLimitChannel.close() } delay(2500) // 13. Channel transformations println("\n13. Channel transformations:") val sourceChannel = Channel() val transformedChannel = Channel() // Transformer coroutine launch { for (value in sourceChannel) { delay(50) transformedChannel.send("Transformed: ${value * 2}") } transformedChannel.close() } // Source producer launch { for (i in 1..5) { sourceChannel.send(i) delay(100) } sourceChannel.close() } // Consumer launch { for (result in transformedChannel) { println(result) } } delay(1000) // 14. Real-world example: job queue println("\n14. Real-world example: job queue") data class Job(val id: Int, val priority: Int) val jobQueue = Channel() // Job processor repeat(2) { processorId -> launch { for (job in jobQueue) { println("Processor $processorId starting job ${job.id} (priority ${job.priority})") delay((100..300).random().toLong()) // Process time varies println("Processor $processorId completed job ${job.id}") } } } // Job submitter launch { val jobs = listOf( Job(1, 1), Job(2, 3), Job(3, 2), Job(4, 1), Job(5, 3) ) // Submit jobs (higher priority first) jobs.sortedByDescending { it.priority } .forEach { job -> jobQueue.send(job) delay(50) // Time between submissions } delay(500) // Wait for processing jobQueue.close() } delay(2000) println("\nChannels demonstration complete!") }

🎨 DSL Design

Domain-Specific Languages (DSLs) in Kotlin leverage the language's expressive syntax to create mini-languages tailored to specific domains. Kotlin DSLs provide type-safe, readable, and maintainable ways to express domain logic through a fluent API. They're built using lambdas with receivers, infix functions, operator overloading, and extension functions, allowing you to create APIs that read like natural language while maintaining compile-time safety.

Lambda with Receiver

The foundation of Kotlin DSLs is the lambda with receiver, which has access to members of a receiver object within the lambda body. The type `T.() -> R` represents a function that can be called on a receiver of type `T`. Inside such a lambda, `this` refers to the receiver, and members can be accessed without qualification. This enables building nested, scoped structures that mirror the problem domain.

Builder Pattern Implementation

DSLs often implement the builder pattern, where a root function (like `html { }`) takes a lambda with receiver to configure a complex object. Inside the lambda, extension functions and properties provide a fluent API. Builders can have nested builders for hierarchical structures. The result is code that clearly expresses structure while hiding implementation details.

Infix Functions for Natural Syntax

Infix functions (marked with `infix`) can be called without dots or parentheses for single parameters, creating natural-looking syntax: `element attribute "value"` instead of `element.attribute("value")`. This is particularly useful in DSLs for assignments and relationships. Infix notation should be used judiciously to maintain readability while providing fluent syntax.

Operator Overloading in DSLs

Operator overloading allows defining behavior for operators like `+`, `-`, `*`, `/`, `in`, etc. In DSLs, operators can represent domain-specific operations. For example, in a CSS DSL, `padding + margin` could combine styles. Operators should be overloaded only when they match the domain's semantics to avoid confusion. Overloaded operators enable concise, expressive DSLs.

Type-Safe Builders

Kotlin DSLs are type-safe: the compiler validates the structure and content. Through clever use of generics and extension functions, DSLs can enforce correct usage patterns. For example, an HTML DSL can ensure tags are properly nested and attributes are valid for each tag. Type safety prevents runtime errors and provides IDE support like autocomplete and validation.

Scope Control and Nesting

DSLs control scope through receiver types. Different nesting levels can have different receivers, providing appropriate functions at each level. `@DslMarker` annotations prevent implicit receivers from outer scopes from being accessed accidentally, forcing explicit qualification. This prevents errors in complex nested DSLs while maintaining clean syntax.

Real-World DSL Examples

Kotlin has several production DSLs: Kotlin HTML DSL for generating HTML, Kotlinx HTML for web templates, Ktor routing DSL for HTTP servers, KotlinTest for testing, Exposed for SQL, and Gradle Kotlin DSL for build scripts. These demonstrate how DSLs make code more declarative and domain-focused. Creating custom DSLs follows the same principles used in these libraries.

// DSL Design Examples fun main() { println("=== DSL Design in Kotlin ===") // 1. Basic DSL with lambda receiver println("\n1. Basic DSL with lambda receiver:") class Person { var name: String = "" var age: Int = 0 var address: String = "" override fun toString(): String { return "Person(name='$name', age=$age, address='$address')" } } fun person(block: Person.() -> Unit): Person { return Person().apply(block) } val john = person { name = "John Doe" age = 30 address = "123 Main St" } println(john) // 2. Nested DSL builders println("\n2. Nested DSL builders:") class Address { var street: String = "" var city: String = "" var zip: String = "" override fun toString(): String { return "$street, $city, $zip" } } class Employee { var name: String = "" var position: String = "" val address = Address() fun address(block: Address.() -> Unit) { address.block() } override fun toString(): String { return "Employee(name='$name', position='$position', address=$address)" } } fun employee(block: Employee.() -> Unit): Employee { return Employee().apply(block) } val alice = employee { name = "Alice Smith" position = "Software Engineer" address { street = "456 Oak Ave" city = "San Francisco" zip = "94107" } } println(alice) // 3. HTML DSL example println("\n3. HTML DSL example:") interface Element { fun render(builder: StringBuilder, indent: String) } class TextElement(val text: String) : Element { override fun render(builder: StringBuilder, indent: String) { builder.append("$indent$text\n") } } abstract class Tag(val name: String) : Element { val children = mutableListOf() val attributes = mutableMapOf() protected fun initTag(tag: T, init: T.() -> Unit): T { tag.init() children.add(tag) return tag } override fun render(builder: StringBuilder, indent: String) { builder.append("$indent<$name${renderAttributes()}>\n") for (c in children) { c.render(builder, "$indent ") } builder.append("$indent\n") } private fun renderAttributes(): String { if (attributes.isEmpty()) return "" return attributes.entries.joinToString(" ", " ") { "${it.key}='${it.value}'" } } override fun toString(): String { val builder = StringBuilder() render(builder, "") return builder.toString() } } class HTML : Tag("html") { fun head(init: Head.() -> Unit) = initTag(Head(), init) fun body(init: Body.() -> Unit) = initTag(Body(), init) } class Head : Tag("head") { fun title(init: Title.() -> Unit) = initTag(Title(), init) } class Body : Tag("body") { fun h1(init: H1.() -> Unit) = initTag(H1(), init) fun p(init: P.() -> Unit) = initTag(P(), init) fun ul(init: UL.() -> Unit) = initTag(UL(), init) } class Title : Tag("title") class H1 : Tag("h1") class P : Tag("p") class UL : Tag("ul") { fun li(init: LI.() -> Unit) = initTag(LI(), init) } class LI : Tag("li") fun html(init: HTML.() -> Unit): HTML { return HTML().apply(init) } val document = html { head { title { children.add(TextElement("My Page")) } } body { h1 { children.add(TextElement("Welcome to Kotlin DSL")) } p { attributes["class"] = "intro" children.add(TextElement("This is a paragraph created with DSL")) } ul { li { children.add(TextElement("First item")) } li { children.add(TextElement("Second item")) } li { children.add(TextElement("Third item")) } } } } println("Generated HTML:") println(document) // 4. Infix functions in DSL println("\n4. Infix functions in DSL:") class Configuration { val settings = mutableMapOf() infix fun String.to(value: Any) { settings[this] = value } infix fun String.by(value: Any) { settings[this] = value } override fun toString(): String { return settings.entries.joinToString("\n") { " ${it.key} = ${it.value}" } } } fun configure(block: Configuration.() -> Unit): Configuration { return Configuration().apply(block) } val config = configure { "server" to "localhost" "port" to 8080 "timeout" by 30 "retries" by 3 } println("Configuration:") println(config) // 5. Operator overloading in DSL println("\n5. Operator overloading in DSL:") data class Vector(val x: Int, val y: Int) { operator fun plus(other: Vector): Vector { return Vector(x + other.x, y + other.y) } operator fun minus(other: Vector): Vector { return Vector(x - other.x, y - other.y) } operator fun times(scalar: Int): Vector { return Vector(x * scalar, y * scalar) } } class VectorBuilder { val vectors = mutableListOf() infix fun Int.and(y: Int) { vectors.add(Vector(this, y)) } infix fun Vector.plus(other: Vector) { vectors.add(this + other) } infix fun Vector.minus(other: Vector) { vectors.add(this - other) } } fun vectors(block: VectorBuilder.() -> Unit): List { return VectorBuilder().apply(block).vectors } val vectorList = vectors { 1 and 2 3 and 4 Vector(1, 2) plus Vector(3, 4) Vector(5, 6) minus Vector(2, 3) Vector(2, 3) * 3 } println("Vectors: $vectorList") // 6. Type-safe SQL-like DSL println("\n6. Type-safe SQL-like DSL:") @DslMarker annotation class SqlDslMarker @SqlDslMarker class QueryBuilder { private val columns = mutableListOf() private var table: String = "" private val conditions = mutableListOf() fun select(vararg cols: String) { columns.addAll(cols) } fun from(tableName: String) { table = tableName } infix fun String.eq(value: Any) { conditions.add("$this = '$value'") } infix fun String.gt(value: Any) { conditions.add("$this > '$value'") } fun build(): String { val cols = if (columns.isEmpty()) "*" else columns.joinToString(", ") val where = if (conditions.isEmpty()) "" else " WHERE " + conditions.joinToString(" AND ") return "SELECT $cols FROM $table$where" } } fun query(block: QueryBuilder.() -> Unit): String { return QueryBuilder().apply(block).build() } val sql = query { select("name", "age", "email") from("users") "age" gt 18 "status" eq "active" } println("Generated SQL: $sql") // 7. DSL for testing println("\n7. DSL for testing:") class TestCase { private val assertions = mutableListOf<() -> Boolean>() private var name: String = "" fun test(name: String, block: TestCase.() -> Unit) { this.name = name block() runTest() } fun expect(condition: Boolean, message: String = "") { assertions.add { if (!condition) println("FAIL: $message") condition } } infix fun T.shouldBe(expected: T) { assertions.add { val passed = this == expected if (!passed) println("FAIL: $this should be $expected") passed } } infix fun T.shouldNotBe(expected: T) { assertions.add { val passed = this != expected if (!passed) println("FAIL: $this should not be $expected") passed } } private fun runTest() { println("\nRunning test: $name") val results = assertions.map { it() } val passed = results.count { it } val total = results.size println("Result: $passed/$total passed") } } fun testCase(block: TestCase.() -> Unit) { TestCase().apply(block) } testCase { test("Basic assertions") { 2 + 2 shouldBe 4 "hello".length shouldBe 5 true shouldNotBe false expect(1 < 2, "1 should be less than 2") } test("String tests") { "Kotlin".startsWith("K") shouldBe true "".isEmpty() shouldBe true } } // 8. DSL for dependency injection println("\n8. DSL for dependency injection:") interface Service class DatabaseService : Service class ApiService : Service class LoggerService : Service class Container { private val services = mutableMapOf Service>() private val instances = mutableMapOf() fun register(name: String, creator: () -> Service) { services[name] = creator } fun resolve(name: String): T { return instances.getOrPut(name) { services[name]?.invoke() ?: throw IllegalArgumentException("Service $name not found") } as T } } fun container(block: Container.() -> Unit): Container { return Container().apply(block) } val diContainer = container { register("database") { DatabaseService() } register("api") { ApiService() } register("logger") { LoggerService() } } val dbService: DatabaseService = diContainer.resolve("database") val apiService: ApiService = diContainer.resolve("api") println("Resolved services: ${dbService::class.simpleName}, ${apiService::class.simpleName}") // 9. DSL for configuration files println("\n9. DSL for configuration files:") data class ServerConfig( var host: String = "localhost", var port: Int = 8080, var ssl: Boolean = false, var timeouts: TimeoutConfig = TimeoutConfig() ) data class TimeoutConfig( var read: Int = 30, var write: Int = 30, var connect: Int = 10 ) fun serverConfig(block: ServerConfig.() -> Unit): ServerConfig { return ServerConfig().apply(block) } fun ServerConfig.timeouts(block: TimeoutConfig.() -> Unit) { timeouts = TimeoutConfig().apply(block) } val server = serverConfig { host = "api.example.com" port = 443 ssl = true timeouts { read = 60 write = 60 connect = 15 } } println("Server config: $server") // 10. DSL for UI layout (conceptual) println("\n10. DSL for UI layout (conceptual):") class View { var width: Int = 100 var height: Int = 50 var margin: Int = 0 var padding: Int = 0 val children = mutableListOf() fun view(block: View.() -> Unit) { children.add(View().apply(block)) } override fun toString(): String { return "View(w=$width, h=$height, margin=$margin, padding=$padding, children=${children.size})" } } fun verticalLayout(block: View.() -> Unit): View { return View().apply(block) } val layout = verticalLayout { width = 300 height = 400 view { width = 280 height = 40 margin = 10 } view { width = 280 height = 200 margin = 10 view { width = 100 height = 30 padding = 5 } } } println("UI Layout: $layout") println("\nDSL design demonstration complete!") }

🤖 Android Development

Android Development with Kotlin represents a paradigm shift from Java, offering more expressive, concise, and safe code. Since Google announced Kotlin as a first-class language for Android in 2017, it has become the preferred choice for new Android projects. Kotlin's null safety, extension functions, coroutines, and DSL capabilities address many pain points in Android development, leading to more robust and maintainable applications.

Kotlin Android Extensions and View Binding

Kotlin Android Extensions (now deprecated) provided synthetic view properties for accessing views without `findViewById()`. Modern Android uses View Binding or Data Binding with Kotlin for type-safe view access. Kotlin's null safety ensures view references are handled safely, preventing common `NullPointerException`s. Extension functions enable adding utility methods to Android classes without inheritance.

Coroutines for Asynchronous Tasks

Coroutines revolutionized async programming on Android, replacing callbacks, AsyncTask, and RxJava for many use cases. With `lifecycleScope` and `viewModelScope`, coroutines automatically follow lifecycle events, preventing memory leaks. `Dispatchers.Main` allows seamless UI updates. Coroutines simplify complex async flows like sequential API calls, parallel processing, and timeout handling.

Android KTX and Jetpack Libraries

Android KTX (Kotlin Extensions) provides Kotlin-friendly extensions for Android framework and Jetpack libraries. These extensions leverage Kotlin features like extension functions, lambdas, and named parameters to make Android APIs more idiomatic. Jetpack libraries like ViewModel, LiveData, Room, and WorkManager have first-class Kotlin support with coroutines and flows.

Architecture Components with Kotlin

Modern Android architecture (MVVM, MVI) pairs well with Kotlin. ViewModels use `viewModelScope` for coroutines. LiveData can be combined with Kotlin flows via `asLiveData()`. Room database uses coroutines for DAO operations. Data Binding works with Kotlin's concise syntax. Kotlin's data classes and sealed classes model state and events cleanly.

Dependency Injection with Kotlin

Dependency injection frameworks like Dagger/Hilt or Koin work seamlessly with Kotlin. Kotlin's concise syntax reduces boilerplate in DI setup. Property delegation (`by inject()`) provides lazy injection. Extension functions can simplify DI configuration. Kotlin's type system helps catch DI errors at compile time rather than runtime.

Testing Android Apps with Kotlin

Kotlin improves Android testing through more expressive test code. Coroutine testing utilities (`TestCoroutineDispatcher`, `runBlockingTest`) enable deterministic async testing. Mocking is easier with Kotlin's default arguments and interfaces. KotlinTest and Spek provide DSL-based testing frameworks. Espresso and UI tests benefit from Kotlin's conciseness.

Performance and Proguard/R8

Kotlin has minimal runtime overhead, and R8/ProGuard optimizes Kotlin code effectively. Inline functions eliminate lambda overhead. Data classes reduce boilerplate without performance cost. Coroutines are lightweight compared to threads. Understanding Kotlin's performance characteristics helps write efficient Android apps.

// Android Development with Kotlin Examples // Note: These are conceptual examples for Android development patterns // Real Android code would require Android framework dependencies fun main() { println("=== Android Development with Kotlin ===") // 1. Android lifecycle-aware coroutines println("\n1. Lifecycle-aware coroutines:") // In a ViewModel: class UserViewModel : ViewModel() { private val _user = MutableLiveData() val user: LiveData = _user fun loadUser(userId: String) { viewModelScope.launch { try { val user = repository.getUser(userId) _user.value = user } catch (e: Exception) { // Handle error } } } } // In an Activity/Fragment: class UserActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Automatic lifecycle cancellation lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.user.collect { user -> updateUI(user) } } } } } // 2. Kotlin extensions for Android println("\n2. Kotlin extensions for Android:") // View extensions fun View.show() { visibility = View.VISIBLE } fun View.hide() { visibility = View.GONE } fun View.disable() { isEnabled = false alpha = 0.5f } fun View.enable() { isEnabled = true alpha = 1.0f } // Context extensions fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, message, duration).show() } fun Context.getColorCompat(@ColorRes colorRes: Int): Int { return ContextCompat.getColor(this, colorRes) } // 3. Android KTX examples println("\n3. Android KTX examples:") // SharedPreferences KTX val sharedPrefs = context.getSharedPreferences("app", Context.MODE_PRIVATE) // Old way: // sharedPrefs.edit().putString("key", "value").apply() // KTX way: sharedPrefs.edit { putString("key", "value") putInt("count", 42) putBoolean("flag", true) } // Bundle KTX val bundle = bundleOf( "id" to 123, "name" to "Kotlin", "active" to true ) // Fragment KTX class MyFragment : Fragment() { private val viewModel: UserViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // Fragment viewLifecycleOwner viewLifecycleOwner.lifecycleScope.launch { viewModel.user.collect { user -> // Update UI } } } } // 4. Room database with Kotlin println("\n4. Room database with Kotlin:") @Entity data class User( @PrimaryKey val id: Int, val name: String, val age: Int, val email: String ) @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): Flow> @Query("SELECT * FROM user WHERE id = :id") suspend fun getById(id: Int): User? @Insert suspend fun insert(user: User) @Update suspend fun update(user: User) @Delete suspend fun delete(user: User) @Query("DELETE FROM user") suspend fun deleteAll() } @Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao companion object { @Volatile private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { INSTANCE ?: buildDatabase(context).also { INSTANCE = it } } } private fun buildDatabase(context: Context): AppDatabase { return Room.databaseBuilder( context, AppDatabase::class.java, "app.db" ).build() } } } // 5. Retrofit with coroutines println("\n5. Retrofit with coroutines:") data class ApiUser( val id: Int, val name: String, val email: String ) interface UserApiService { @GET("users/{id}") suspend fun getUser(@Path("id") id: Int): ApiUser @GET("users") suspend fun getUsers(): List @POST("users") suspend fun createUser(@Body user: ApiUser): ApiUser @Multipart @POST("upload") suspend fun uploadImage(@Part image: MultipartBody.Part): ResponseBody } class UserRepository(private val api: UserApiService) { suspend fun getUser(id: Int): User { return try { val apiUser = api.getUser(id) // Convert API model to domain model User(apiUser.id, apiUser.name, 0, apiUser.email) } catch (e: Exception) { throw UserNotFoundException(id) } } suspend fun getUsers(): List { return api.getUsers().map { apiUser -> User(apiUser.id, apiUser.name, 0, apiUser.email) } } } class UserNotFoundException(id: Int) : Exception("User $id not found") // 6. WorkManager with coroutines println("\n6. WorkManager with coroutines:") class UploadWorker( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { return try { // Upload data val data = inputData.getString("data") println("Uploading: $data") delay(5000) // Simulate upload Result.success() } catch (e: Exception) { Result.retry() } } } // Schedule work fun scheduleUpload(context: Context, data: String) { val workRequest = OneTimeWorkRequestBuilder() .setInputData(workDataOf("data" to data)) .setConstraints( Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() ) .build() WorkManager.getInstance(context).enqueue(workRequest) } // 7. View Binding with Kotlin println("\n7. View Binding with Kotlin:") // activity_main.xml -> ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // Type-safe view access binding.textView.text = "Hello Kotlin!" binding.button.setOnClickListener { binding.textView.text = "Button clicked!" } // Extension for simpler binding binding.apply { textView.text = "Using apply" button.isEnabled = true progressBar.visibility = View.VISIBLE } } } // 8. Dependency Injection with Koin println("\n8. Dependency Injection with Koin:") // Module definitions val appModule = module { single { AppDatabase.getInstance(get()) } single { get().userDao() } single { UserRepositoryImpl(get()) } viewModel { UserViewModel(get()) } } // Repository implementation class UserRepositoryImpl(private val dao: UserDao) : UserRepository { override suspend fun getUsers(): List = dao.getAll() override suspend fun getUser(id: Int): User? = dao.getById(id) } // ViewModel with injected repository class UserViewModel(private val repository: UserRepository) : ViewModel() { val users: LiveData> = repository.getUsers().asLiveData() fun loadUser(id: Int) = viewModelScope.launch { val user = repository.getUser(id) // Process user } } // 9. Testing with Kotlin println("\n9. Testing with Kotlin:") @RunWith(AndroidJUnit4::class) class UserViewModelTest { private lateinit var viewModel: UserViewModel private lateinit var repository: FakeUserRepository @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() @Before fun setup() { repository = FakeUserRepository() viewModel = UserViewModel(repository) } @Test fun `loadUsers should emit users`() = runBlockingTest { // Given val testUsers = listOf( User(1, "Alice", 25, "alice@example.com"), User(2, "Bob", 30, "bob@example.com") ) repository.addUsers(testUsers) // When val result = viewModel.users.getOrAwaitValue() // Then assertEquals(2, result.size) assertEquals("Alice", result[0].name) } @Test fun `loadUser should return user`() = runBlockingTest { // Given val testUser = User(1, "Test", 25, "test@example.com") repository.addUser(testUser) // When viewModel.loadUser(1) // Then - verify repository was called // (implementation depends on how ViewModel exposes results) } } class FakeUserRepository : UserRepository { private val users = mutableListOf() override suspend fun getUsers(): List = users override suspend fun getUser(id: Int): User? { return users.find { it.id == id } } fun addUser(user: User) { users.add(user) } fun addUsers(userList: List) { users.addAll(userList) } } // 10. Modern Android architecture with Kotlin println("\n10. Modern Android architecture:") // State representation sealed class UserState { object Loading : UserState() data class Success(val users: List) : UserState() data class Error(val message: String) : UserState() } // Events sealed class UserEvent { object LoadUsers : UserEvent() data class SelectUser(val id: Int) : UserEvent() object Refresh : UserEvent() } // ViewModel with StateFlow class ModernUserViewModel(private val repository: UserRepository) : ViewModel() { private val _state = MutableStateFlow(UserState.Loading) val state: StateFlow = _state private val _events = MutableSharedFlow() val events: Flow = _events init { viewModelScope.launch { _events.collect { event -> when (event) { is UserEvent.LoadUsers -> loadUsers() is UserEvent.SelectUser -> selectUser(event.id) UserEvent.Refresh -> refresh() } } } } private suspend fun loadUsers() { _state.value = UserState.Loading try { val users = repository.getUsers() _state.value = UserState.Success(users) } catch (e: Exception) { _state.value = UserState.Error(e.message ?: "Unknown error") } } private fun selectUser(id: Int) { // Handle user selection println("User $id selected") } private fun refresh() { viewModelScope.launch { loadUsers() } } } // 11. Compose with Kotlin (declarative UI) println("\n11. Jetpack Compose with Kotlin:") /* @Composable fun UserList(users: List, onUserClick: (User) -> Unit) { LazyColumn { items(users) { user -> UserCard(user = user, onUserClick = onUserClick) } } } @Composable fun UserCard(user: User, onUserClick: (User) -> Unit) { Card( modifier = Modifier .fillMaxWidth() .padding(8.dp) .clickable { onUserClick(user) }, elevation = 4.dp ) { Column(modifier = Modifier.padding(16.dp)) { Text( text = user.name, style = MaterialTheme.typography.h6 ) Spacer(modifier = Modifier.height(4.dp)) Text( text = user.email, style = MaterialTheme.typography.body2 ) } } } @Composable fun UserScreen(viewModel: UserViewModel = viewModel()) { val userState by viewModel.state.collectAsState() when (val state = userState) { is UserState.Loading -> { Box(modifier = Modifier.fillMaxSize()) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } } is UserState.Success -> { UserList(users = state.users) { user -> // Handle click } } is UserState.Error -> { Text(text = "Error: ${state.message}") } } } */ println("\nAndroid development with Kotlin demonstration complete!") } // Stub classes for Android examples open class ViewModel { val viewModelScope = MainScope() } class MutableLiveData { var value: T? = null } typealias LiveData = MutableLiveData class MainScope class AppCompatActivity { val lifecycleScope = MainScope() } class Bundle class Context class View { var visibility: Int = 0 var isEnabled: Boolean = true var alpha: Float = 1.0f } object View { const val VISIBLE = 0 const val GONE = 1 } class Toast { companion object { fun makeText(context: Context, message: String, duration: Int): Toast = Toast() const val LENGTH_SHORT = 0 } fun show() {} } object ContextCompat { fun getColor(context: Context, colorRes: Int): Int = 0 } class Room { companion object { fun databaseBuilder(context: Context, klass: Class<*>, name: String): Builder = Builder() } class Builder { fun build(): Any = Any() } } object RoomDatabase annotation class Entity annotation class PrimaryKey annotation class Dao annotation class Query(val value: String) annotation class Insert annotation class Update annotation class Delete annotation class Database(val entities: Array>, val version: Int) annotation class Volatile annotation class ColorRes annotation class GET(val value: String) annotation class POST(val value: String) annotation class Path(val value: String) annotation class Body annotation class Multipart annotation class Part class MultipartBody { class Part } class ResponseBody class WorkerParameters class CoroutineWorker(context: Context, params: WorkerParameters) class Result { companion object { fun success(): Result = Result() fun retry(): Result = Result() } } class InputData { fun getString(key: String): String? = null } class WorkManager { companion object { fun getInstance(context: Context): WorkManager = WorkManager() } fun enqueue(workRequest: Any) {} } class OneTimeWorkRequestBuilder { fun setInputData(data: Any): OneTimeWorkRequestBuilder = this fun setConstraints(constraints: Any): OneTimeWorkRequestBuilder = this fun build(): Any = Any() } fun workDataOf(vararg pairs: Pair): Any = Any() class Constraints { class Builder { fun setRequiredNetworkType(type: Any): Builder = this fun build(): Constraints = Constraints() } } object NetworkType { const val CONNECTED = 0 } class ActivityMainBinding { companion object { fun inflate(layoutInflater: Any): ActivityMainBinding = ActivityMainBinding() } val root: Any = Any() val textView = TextView() val button = Button() val progressBar = ProgressBar() } class TextView { var text: String = "" } class Button { fun setOnClickListener(listener: () -> Unit) {} var isEnabled: Boolean = true } class ProgressBar { var visibility: Int = 0 } class LayoutInflater object module { fun single(creator: () -> Any) {} fun viewModel(creator: () -> Any) {} } inline fun viewModels(): Lazy = lazy { T::class.java.newInstance() as T } class Fragment { val viewLifecycleOwner = ViewLifecycleOwner() } class ViewLifecycleOwner { val lifecycleScope = MainScope() } object Lifecycle { enum class State { STARTED } } suspend fun repeatOnLifecycle(state: Lifecycle.State, block: suspend () -> Unit) {} annotation class RunWith(val value: Class<*>) object AndroidJUnit4::class annotation class Before annotation class Test annotation class Rule annotation class get:Rule class InstantTaskExecutorRule fun LiveData.getOrAwaitValue(): T = value as T

🌐 Kotlin Multiplatform

Kotlin Multiplatform (KMP) allows sharing code between different platforms while accessing platform-specific APIs when needed. It enables writing business logic, data models, and other non-UI code once and reusing it across Android, iOS, web, desktop, and server applications. KMP is not a write-once-run-anywhere solution but a share-what-makes-sense approach that respects platform differences while maximizing code reuse.

Shared Common Code

The core of KMP is the common module containing platform-agnostic code written in pure Kotlin. This code can define expect declarations for platform-specific APIs. Common code can use a subset of Kotlin standard library and multiplatform libraries. It's compiled to platform-specific binaries (JVM bytecode, JavaScript, or native code) for each target platform.

Expect/Actual Mechanism

The expect/actual pattern bridges platform differences: `expect` declarations in common code specify required API contracts, while `actual` implementations in platform-specific modules provide platform-specific implementations. This works for functions, properties, classes, and objects. The compiler ensures expect and actual declarations match, providing type safety across platforms.

Platform-Specific Implementations

For each target platform (iOS, Android, JS, etc.), platform-specific modules provide actual implementations of expected declarations. These modules can access full platform APIs (Android SDK, iOS frameworks, browser APIs, etc.). Platform modules depend on the common module and can also contain platform-specific code that doesn't need sharing.

Multiplatform Libraries

Kotlin Multiplatform has a growing ecosystem of libraries: Ktor for networking, Serialization for JSON, Coroutines for async, DateTime for dates, SQLDelight for databases, and more. These libraries provide common APIs with platform-specific implementations. You can also create your own multiplatform libraries to share across projects.

iOS Integration with Kotlin/Native

Kotlin/Native compiles Kotlin to native binaries for iOS (and other platforms). It provides interoperability with Objective-C and Swift through automatic framework generation. Kotlin classes are exposed as Objective-C/Swift classes, and vice versa. Memory management uses automatic reference counting (ARC) compatible with iOS's memory model.

Web Integration with Kotlin/JS

Kotlin/JS compiles Kotlin to JavaScript for web applications. It can interoperate with existing JavaScript libraries through external declarations. Kotlin/JS supports both browser and Node.js targets. For UI, you can use React bindings, Compose for Web, or interoperate with any JavaScript framework.

Build Configuration and Gradle

KMP uses Gradle with the Kotlin Multiplatform plugin. Build files define targets (ios, android, js, jvm) and source sets (commonMain, iosMain, androidMain, etc.). Dependencies can be common (shared) or platform-specific. The build produces platform-specific artifacts (Android AAR, iOS framework, JS bundle, JAR).

// Kotlin Multiplatform Examples // Note: Multiplatform code is organized across multiple files/modules // This is a conceptual demonstration fun main() { println("=== Kotlin Multiplatform (KMP) ===") // 1. Common module structure println("\n1. Common module structure:") /* // In commonMain sources: // Shared data models data class User( val id: String, val name: String, val email: String ) data class ApiResponse( val success: Boolean, val data: T?, val error: String? = null ) // Shared business logic class UserValidator { fun validateEmail(email: String): Boolean { return email.contains("@") } fun validateName(name: String): Boolean { return name.length in 2..50 } } // Shared interfaces interface UserRepository { suspend fun getUsers(): List suspend fun getUser(id: String): User? suspend fun saveUser(user: User): Boolean } // Shared use cases class GetUserUseCase(private val repository: UserRepository) { suspend operator fun invoke(id: String): User? { return repository.getUser(id) } } */ // 2. Expect/Actual pattern println("\n2. Expect/Actual pattern:") /* // In commonMain: expect class Platform() { val platform: String fun getCurrentTime(): Long } // In androidMain: actual class Platform actual constructor() { actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}" actual fun getCurrentTime(): Long { return System.currentTimeMillis() } } // In iosMain: actual class Platform actual constructor() { actual val platform: String = "iOS" actual fun getCurrentTime(): Long { return platform.posix.gettimeofday().use { tv -> tv.tv_sec * 1000L + tv.tv_usec / 1000L } } } // In jsMain: actual class Platform actual constructor() { actual val platform: String = "JS" actual fun getCurrentTime(): Long { return kotlin.js.Date.now().toLong() } } // Usage in common code: fun printPlatformInfo() { val platform = Platform() println("Running on: ${platform.platform}") println("Current time: ${platform.getCurrentTime()}") } */ // 3. Sharing network logic with Ktor println("\n3. Sharing network logic:") /* // In commonMain: import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.request.* class ApiClient(private val client: HttpClient) { suspend fun getUsers(): List { return client.get("https://api.example.com/users").body() } suspend fun getUser(id: String): User { return client.get("https://api.example.com/users/$id").body() } } // In build.gradle.kts (common): // kotlin { // sourceSets { // val commonMain by getting { // dependencies { // implementation("io.ktor:ktor-client-core:$ktorVersion") // } // } // } // } // Platform-specific HTTP engine configuration: // - Android: OkHttp // - iOS: NSURLSession // - JS: Fetch or XMLHttpRequest // - JVM: Apache or CIO */ // 4. Sharing database logic with SQLDelight println("\n4. Sharing database logic:") /* // In commonMain/sqldelight: // Users.sq: CREATE TABLE User ( id TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL ); selectAll: SELECT * FROM User; insertUser: INSERT OR REPLACE INTO User (id, name, email) VALUES (?, ?, ?); // Generated Kotlin code is available in commonMain class UserDataSource(private val database: Database) { fun getAllUsers(): List { return database.userQueries.selectAll().executeAsList() .map { User(it.id, it.name, it.email) } } fun insertUser(user: User) { database.userQueries.insertUser(user.id, user.name, user.email) } } */ // 5. Sharing serialization with kotlinx.serialization println("\n5. Sharing serialization:") /* // In commonMain: import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable data class ApiUser( val id: String, val name: String, val email: String, @SerialName("avatar_url") val avatarUrl: String? ) object JsonParser { private val json = Json { ignoreUnknownKeys = true isLenient = true } fun parseUser(jsonString: String): ApiUser { return json.decodeFromString(jsonString) } fun toJson(user: ApiUser): String { return json.encodeToString(user) } } // Same code works on all platforms */ // 6. Dependency injection in KMP println("\n6. Dependency injection in KMP:") /* // In commonMain: expect class PlatformDependencies { val httpClient: HttpClient val database: Database val preferences: Preferences } // In common code: class AppContainer(platformDeps: PlatformDependencies) { val userRepository: UserRepository by lazy { UserRepositoryImpl( apiClient = ApiClient(platformDeps.httpClient), database = platformDeps.database ) } val getUserUseCase: GetUserUseCase by lazy { GetUserUseCase(userRepository) } } // Platform-specific implementations provide actual dependencies */ // 7. ViewModel sharing (with extra libraries) println("\n7. ViewModel sharing:") /* // Using Moko-MVVM or Decompose libraries // In commonMain: class SharedViewModel( private val getUserUseCase: GetUserUseCase ) : ViewModel() { private val _state = MutableStateFlow(UserState.Loading) val state: StateFlow = _state fun loadUser(id: String) { viewModelScope.launch { _state.value = UserState.Loading try { val user = getUserUseCase(id) _state.value = UserState.Success(user) } catch (e: Exception) { _state.value = UserState.Error(e.message ?: "Unknown error") } } } } sealed class UserState { object Loading : UserState() data class Success(val user: User?) : UserState() data class Error(val message: String) : UserState() } // Android: Use directly with Android ViewModel // iOS: Bind to SwiftUI/UIKit via library bindings */ // 8. Testing shared code println("\n8. Testing shared code:") /* // In commonTest: class UserValidatorTest { private val validator = UserValidator() @Test fun testValidEmail() { assertTrue(validator.validateEmail("test@example.com")) assertFalse(validator.validateEmail("invalid-email")) } @Test fun testValidName() { assertTrue(validator.validateName("John")) assertFalse(validator.validateName("A")) // Too short assertFalse(validator.validateName("A".repeat(51))) // Too long } } // Tests run on all platforms or just JVM */ // 9. Building for multiple platforms println("\n9. Build configuration example:") /* // build.gradle.kts: plugins { kotlin("multiplatform") version "1.8.0" } kotlin { android { compileSdk = 33 } ios { binaries { framework { baseName = "Shared" } } } js(IR) { browser { testTask { useKarma { useChromeHeadless() } } } } jvm() sourceSets { val commonMain by getting { dependencies { implementation("io.ktor:ktor-client-core:2.2.4") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") } } val androidMain by getting { dependencies { implementation("io.ktor:ktor-client-okhttp:2.2.4") } } val iosMain by getting { dependencies { implementation("io.ktor:ktor-client-darwin:2.2.4") } } } } */ // 10. Real-world sharing example println("\n10. Real-world sharing example:") /* // Authentication flow that can be shared: // In commonMain: class AuthenticationManager( private val apiClient: ApiClient, private val tokenStorage: TokenStorage ) { suspend fun login(email: String, password: String): AuthResult { return try { val response = apiClient.post( "auth/login", LoginRequest(email, password) ) if (response.success) { tokenStorage.saveToken(response.token) AuthResult.Success } else { AuthResult.Error(response.message ?: "Login failed") } } catch (e: Exception) { AuthResult.Error("Network error: ${e.message}") } } suspend fun logout() { tokenStorage.clearToken() } fun isAuthenticated(): Boolean { return tokenStorage.getToken() != null } } sealed class AuthResult { object Success : AuthResult() data class Error(val message: String) : AuthResult() } // Platform-specific TokenStorage implementations: // Android: SharedPreferences // iOS: Keychain/UserDefaults // Web: LocalStorage */ // 11. Platform-specific UI with shared logic println("\n11. Platform-specific UI with shared logic:") /* // Shared ViewModel (common): class LoginViewModel( private val authManager: AuthenticationManager ) : ViewModel() { private val _uiState = MutableStateFlow(LoginUiState.Idle) val uiState: StateFlow = _uiState fun login(email: String, password: String) { viewModelScope.launch { _uiState.value = LoginUiState.Loading val result = authManager.login(email, password) _uiState.value = when (result) { is AuthResult.Success -> LoginUiState.Success is AuthResult.Error -> LoginUiState.Error(result.message) } } } } sealed class LoginUiState { object Idle : LoginUiState() object Loading : LoginUiState() object Success : LoginUiState() data class Error(val message: String) : LoginUiState() } // Android UI (androidMain): class LoginActivity : AppCompatActivity() { private val viewModel: LoginViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> when (state) { is LoginUiState.Loading -> showLoading() is LoginUiState.Success -> navigateToHome() is LoginUiState.Error -> showError(state.message) else -> {} } } } } } } // iOS UI (Swift): struct LoginView: View { @StateObject private var viewModel = LoginViewModel() var body: some View { VStack { TextField("Email", text: $email) SecureField("Password", text: $password) Button("Login") { viewModel.login(email: email, password: password) } .disabled(viewModel.uiState.isLoading) if case .error(let message) = viewModel.uiState { Text(message).foregroundColor(.red) } } } } */ // 12. Code sharing metrics and benefits println("\n12. Code sharing benefits:") /* Typical sharing percentages: - Business logic: 70-90% shared - Data models: 100% shared - Networking: 80-90% shared - Database: 70-80% shared - UI: 0% shared (platform-specific) Benefits: 1. Consistent behavior across platforms 2. Reduced development time 3. Easier maintenance (fix once) 4. Shared tests 5. Team knowledge sharing 6. Faster feature rollout */ println("\nKotlin Multiplatform demonstration complete!") } // Stub classes for examples class HttpClient class Database class Preferences class TokenStorage { fun saveToken(token: String) {} fun getToken(): String? = null fun clearToken() {} } data class LoginRequest(val email: String, val password: String) data class LoginResponse(val success: Boolean, val token: String?, val message: String?) class ViewModel { val viewModelScope = MainScope() } class MutableStateFlow(initialValue: T) { var value: T = initialValue } typealias StateFlow = MutableStateFlow