Kotlin Interview Questions with Answers
Most Asked Kotlin Interview Questions for Software Engineer Roles
Introduction
Kotlin is a modern, statically typed programming language that runs on the JVM and is fully interoperable with Java. Developed by JetBrains, Kotlin is designed to be concise, safe, and pragmatic. It has become the preferred language for Android development, officially supported by Google, and is increasingly used for backend, web, and cross-platform development. This comprehensive guide presents 100+ carefully curated Kotlin interview questions and answers, covering everything from the fundamentals to advanced topics. You'll master variables, data types, functions, classes, null safety, data classes, sealed classes, extension functions, higher-order functions, coroutines, flows, channels, generics, delegation, DSL, reflection, and real-world Android and backend development scenarios. Whether you're preparing for an Android developer role, a backend position with Kotlin, or a full-stack job, this question bank will solidify your understanding and give you the confidence to ace your interview. Start practicing now and become a Kotlin expert.
Why Kotlin?
- 100% interoperable with Java – leverage existing Java libraries and frameworks
- Null safety – eliminates null pointer exceptions with compile-time checks
- Concise and expressive syntax – reduces boilerplate code significantly
- Built-in coroutines – simplifies asynchronous programming and concurrency
- Officially supported for Android development by Google
- Growing ecosystem with full support for backend development (Spring Boot, Ktor)
- Used by companies like Google, Netflix, Uber, and many more
Most Asked Kotlin Interview Questions
Kotlin is a modern, statically-typed programming language that runs on the JVM. It is designed to be concise, safe, interoperable with Java, and pragmatic.
- Statically-typed: Type checking at compile time
- Concise: Less boilerplate than Java
- Null safety: Built-in null safety features
- Interoperable: 100% compatible with Java
- Multi-platform: JVM, JS, Native
// Hello World in Kotlin
fun main() {
println("Hello, World!")
}Variables in Kotlin are declared using var (mutable) and val (immutable) keywords. Type inference is supported.
- val: Read-only variable (immutable)
- var: Mutable variable
- Type inference: Types can be omitted
- Null safety:
Type?for nullable types - Lateinit: Late initialization for non-null types
// Variables in Kotlin
var mutableVar: String = "Hello" // Mutable variable
val immutableVal: String = "World" // Immutable variable (read-only)
var inferred = "Inferred Type" // Type inference
// Nullable variables
var nullable: String? = null
println(mutableVar)
println(immutableVal)
println(inferred)
println(nullable)Kotlin has a rich type system with both primitive and reference types. All types are objects in Kotlin.
- Numbers: Int, Long, Float, Double, Short, Byte
- Boolean: true/false
- Char: Single character
- String: Sequence of characters
- Array: Array of elements
- Collections: List, Set, Map
// Data Types in Kotlin
// Numbers
val intNum: Int = 10
val longNum: Long = 100L
val floatNum: Float = 3.14f
val doubleNum: Double = 3.14159
val shortNum: Short = 100
val byteNum: Byte = 127
// Booleans
val isActive: Boolean = true
// Characters
val char: Char = 'A'
// Strings
val str: String = "Hello Kotlin"
// Arrays
val arr: Array<Int> = arrayOf(1, 2, 3, 4, 5)
// Type checking
println(intNum is Int) // true
println(str is String) // trueFunctions in Kotlin are defined with the fun keyword. They support default parameters, named arguments, and higher-order functions.
- Basic:
fun name(parameters): ReturnType - Single-expression:
fun add(a: Int, b: Int) = a + b - Default params:
fun greet(name: String = "Guest") - Unit return:
fun print(): Unit - Higher-order: Functions that take/return functions
// Functions in Kotlin
// Basic function
fun add(a: Int, b: Int): Int {
return a + b
}
// Single-expression function
fun subtract(a: Int, b: Int) = a - b
// Default parameters
fun greet(name: String = "Guest"): String {
return "Hello, $name!"
}
// Function with unit return (void)
fun printMessage(message: String): Unit {
println(message)
}
// Higher-order function
fun operate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
// Lambda expression
val multiply = { a: Int, b: Int -> a * b }
// Inline function
inline fun inlineExample() {
println("Inline function")
}
// Usage
println(add(5, 3))
println(subtract(10, 4))
println(greet("Alice"))
printMessage("Hello")
println(operate(6, 7, multiply))Arrays are collections of elements with a fixed size. Kotlin provides both Array class and primitive type arrays.
- Creation:
arrayOf(),intArrayOf() - Access:
arr[index] - Operations:
size,map,filter - Primitive arrays:
IntArray,DoubleArray - Lists vs Arrays: Lists are dynamic, arrays are fixed
// Arrays in Kotlin
// Array creation
val numbers = arrayOf(1, 2, 3, 4, 5)
val strings = arrayOf("Apple", "Banana", "Orange")
val mixed = arrayOf(1, "Hello", 3.14)
// Primitive arrays
val intArray = intArrayOf(1, 2, 3)
val doubleArray = doubleArrayOf(1.0, 2.0, 3.0)
// Array operations
println(numbers.size)
println(numbers[2]) // Access element
numbers[2] = 10 // Modify element
// Iteration
for (num in numbers) {
println(num)
}
// Array functions
val doubled = numbers.map { it * 2 }
val filtered = numbers.filter { it > 2 }
val sum = numbers.sum()
// List vs Array
val list = listOf(1, 2, 3) // Immutable list
val mutableList = mutableListOf(1, 2, 3) // Mutable list
println(doubled)
println(filtered)
println(sum)Kotlin collections include List, Set, and Map with both immutable and mutable variants.
- List: Ordered collection with duplicates
- Set: Unordered collection without duplicates
- Map: Key-value pairs
- Immutable:
listOf(),setOf(),mapOf() - Mutable:
mutableListOf(),mutableSetOf() - Operations:
filter,map,reduce
// Collections in Kotlin
// List
val immutableList = listOf(1, 2, 3, 4, 5)
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4)
mutableList.remove(2)
// Set
val immutableSet = setOf(1, 2, 3, 3) // [1, 2, 3]
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
// Map
val immutableMap = mapOf("key1" to "value1", "key2" to "value2")
val mutableMap = mutableMapOf("key1" to "value1")
mutableMap["key2"] = "value2"
mutableMap.remove("key1")
// Collection operations
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evens = numbers.filter { it % 2 == 0 }
val doubled = numbers.map { it * 2 }
val sum = numbers.reduce { acc, i -> acc + i }
val exists = numbers.any { it > 10 }
val allEven = numbers.all { it % 2 == 0 }
println(evens)
println(doubled)
println(sum)Data classes are special classes that automatically provide toString(), equals(), hashCode(), and copy() methods.
- Declaration:
data class Person(val name: String) - Copy:
person.copy(name = "NewName") - Destructuring:
val (name, age) = person - Component functions: Automatically generated
- Limitations: Cannot be abstract, open, sealed, or inner
// Data Classes in Kotlin
// Data class automatically provides toString, equals, hashCode, copy
data class Person(
val name: String,
val age: Int,
val city: String = "Unknown"
)
// Usage
val person1 = Person("Alice", 25, "NYC")
val person2 = person1.copy(age = 26) // Copy with modification
val (name, age, city) = person1 // Destructuring
println(person1) // Person(name=Alice, age=25, city=NYC)
println(person2)
println("Name: $name, Age: $age, City: $city")
// Data class with validation
data class User(val username: String, val email: String) {
init {
require(username.isNotEmpty()) { "Username cannot be empty" }
require(email.contains("@")) { "Invalid email" }
}
}Sealed classes are used to represent restricted class hierarchies. They are similar to enums but with more flexibility.
- Declaration:
sealed class Result - Subclasses: Defined in the same file
- When expressions: Exhaustive when used
- Data classes: Can be combined with data classes
- Sealed interfaces: Available in Kotlin 1.5+
// Sealed Classes in Kotlin
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
// Usage
fun handleResult(result: Result) {
when (result) {
is Result.Success -> println("Success: ${result.data}")
is Result.Error -> println("Error: ${result.message}")
Result.Loading -> println("Loading...")
}
}
// Sealed interface
sealed interface Shape {
data class Circle(val radius: Double) : Shape
data class Rectangle(val width: Double, val height: Double) : Shape
object Point : Shape
}
fun area(shape: Shape): Double = when (shape) {
is Shape.Circle -> Math.PI * shape.radius * shape.radius
is Shape.Rectangle -> shape.width * shape.height
Shape.Point -> 0.0
}
// Usage
handleResult(Result.Success("Data loaded"))
println(area(Shape.Circle(5.0)))Kotlin's null safety system helps prevent NullPointerException by distinguishing between nullable and non-nullable types.
- Non-nullable:
Stringcannot be null - Nullable:
String?can be null - Safe call:
?.letoperator - Elvis operator:
?:for default values - Not-null assertion:
!!(use carefully)
// Null Safety in Kotlin
// Nullable types
var nullableString: String? = null
var nonNullableString: String = "Hello"
// Safe call operator
val length = nullableString?.length // Returns null if nullableString is null
// Elvis operator
val lengthOrZero = nullableString?.length ?: 0
// Not-null assertion (use carefully!)
val lengthForced = nullableString!!.length // Throws NPE if null
// Safe casting
val obj: Any = "Hello"
val str = obj as? String // Safe cast, returns null if cast fails
// Let function for null checks
nullableString?.let {
println("String is: $it")
println("Length: ${it.length}")
}
// Run function
nullableString?.run {
println("String length: $length")
}
// Also function
nullableString?.also {
println("Processing: $it")
}
// TakeIf and TakeUnless
val result = nullableString?.takeIf { it.length > 0 }
val result2 = nullableString?.takeUnless { it.isNullOrEmpty() }Kotlin provides standard control flow statements including if, when, for, while, and do-while loops.
- If-else: Can be used as expression
- When: Switch replacement with powerful features
- For loops:
for (i in 1..10) - While loops:
while (condition) - Do-while:
do while (condition)
// Control Flow in Kotlin
// If-else expression
val age = 25
val status = if (age < 18) "Minor" else "Adult"
println(status)
// When expression (switch replacement)
val grade = 'A'
val result = when (grade) {
'A' -> "Excellent"
'B' -> "Good"
'C' -> "Fair"
else -> "Needs Improvement"
}
println(result)
// When with ranges
val score = 85
val grade2 = when (score) {
in 90..100 -> "A"
in 80..89 -> "B"
in 70..79 -> "C"
else -> "F"
}
// For loop
for (i in 1..5) {
println(i)
}
// For loop with step
for (i in 1..10 step 2) {
println(i)
}
// For loop down to
for (i in 10 downTo 1) {
println(i)
}
// While loop
var i = 0
while (i < 5) {
println(i)
i++
}
// Do-while loop
do {
println(i)
i--
} while (i > 0)Kotlin supports object-oriented programming with classes, inheritance, and interfaces. Classes are final by default unless marked open.
- Class:
class Person(val name: String) - Inheritance:
open class Animal - Override:
override fun method() - Interfaces:
interface Flyable - Abstract classes:
abstract class Vehicle
// Classes and Inheritance in Kotlin
// Base class
open class Animal(val name: String) {
open fun makeSound() {
println("Animal sound")
}
}
// Derived class
class Dog(name: String, val breed: String) : Animal(name) {
override fun makeSound() {
println("Woof!")
}
}
// Abstract class
abstract class Vehicle {
abstract fun start()
fun stop() {
println("Stopped")
}
}
// Interface
interface Flyable {
fun fly()
fun land() {
println("Landing...")
}
}
// Multiple interfaces
interface Swimmable {
fun swim()
}
class Duck : Flyable, Swimmable {
override fun fly() {
println("Flying")
}
override fun swim() {
println("Swimming")
}
}
// Usage
val dog = Dog("Rex", "German Shepherd")
dog.makeSound()
println(dog.name)
val duck = Duck()
duck.fly()
duck.swim()Properties in Kotlin are similar to fields but with built-in getters and setters. They can be read-only (val) or mutable (var).
- Backing field:
fieldkeyword - Custom getter:
get() = field.uppercase() - Custom setter:
set(value) { field = value } - Lateinit:
lateinit var name: String - Lazy:
val data by lazy { }
// Properties in Kotlin
class Person {
// Property with getter and setter
var name: String = ""
get() = field.uppercase()
set(value) {
field = value.trim()
}
// Read-only property
val fullName: String
get() = "$firstName $lastName"
// Late initialization
lateinit var address: String
// Lazy initialization
val expensiveData: String by lazy {
println("Computing expensive data...")
"Expensive Result"
}
// Backing field
var age: Int = 0
set(value) {
if (value >= 0) {
field = value
}
}
// Property delegate
var email: String by Delegates.observable("") { prop, old, new ->
println("Email changed from $old to $new")
}
}
// Usage
val person = Person()
person.name = " Alice "
println(person.name) // ALICE
println(person.expensiveData) // Computed lazily
person.age = 25
person.email = "alice@example.com"Companion objects are used to define class-level members similar to static members in Java. They can have properties and functions.
- Declaration:
companion object { } - Constants:
const val TAG = "Class" - Factory: Can be used as factory for instances
- Named:
companion object Factory - Interfaces: Can implement interfaces
// Companion Objects in Kotlin
class MyClass {
companion object {
const val TAG = "MyClass"
var counter = 0
fun create(): MyClass {
return MyClass()
}
}
}
// Companion object with name
class AnotherClass {
companion object Factory {
fun create(): AnotherClass = AnotherClass()
}
}
// Companion object in interface
interface Factory<T> {
fun create(): T
}
class Product {
companion object : Factory<Product> {
override fun create(): Product = Product()
}
}
// Usage
println(MyClass.TAG)
MyClass.counter++
val instance = MyClass.create()
val another = AnotherClass.Factory.create()
val product = Product.create()Kotlin uses try-catch-finally blocks for exception handling. It also provides the use function for resource management.
- Try-catch:
try { } catch (e: Exception) { } - Try as expression:
val result = try { } catch { } - Custom exceptions:
class MyException : Exception() - Finally:
finally { } - Use function:
writer.use { }for auto-closable
// Exception Handling in Kotlin
// Try-catch block
fun divide(a: Int, b: Int): Int {
return try {
a / b
} catch (e: ArithmeticException) {
println("Division by zero!")
0
}
}
// Try as expression
val result = try {
val x = 10 / 0
"Success"
} catch (e: Exception) {
"Error: ${e.message}"
}
// Custom exception
class InvalidAgeException(message: String) : Exception(message)
fun validateAge(age: Int) {
if (age < 0 || age > 150) {
throw InvalidAgeException("Invalid age: $age")
}
}
// Finally block
fun readFile() {
try {
println("Reading file...")
// File operations
} catch (e: Exception) {
println("Error reading file: ${e.message}")
} finally {
println("Closing resources...")
}
}
// Use function for auto-closable resources
fun writeToFile() {
val writer = java.io.FileWriter("test.txt")
writer.use {
it.write("Hello Kotlin")
}
}
// Usage
println(divide(10, 2))
println(divide(10, 0))
try {
validateAge(200)
} catch (e: InvalidAgeException) {
println(e.message)
}Lambda expressions are anonymous functions that can be treated as values. They are widely used in functional programming and collections.
- Syntax:
{ x -> x * x } - it keyword:
{ it * 2 }for single parameter - Higher-order: Functions taking lambdas
- Function reference:
::functionName - Return: Last expression is the return value
// Lambda Expressions in Kotlin
// Basic lambda
val square: (Int) -> Int = { x -> x * x }
val square2 = { x: Int -> x * x }
// Lambda with it
val doubled = { x: Int -> x * 2 }
val doubled2: (Int) -> Int = { it * 2 }
// Higher-order functions
fun performOperation(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
return operation(x, y)
}
// Lambda with multiple lines
val complexOperation = { x: Int ->
val y = x * 2
y + 10
}
// Function reference
fun multiply(x: Int, y: Int) = x * y
val multiplyRef = ::multiply
// Returning lambda from function
fun getOperation(type: String): (Int, Int) -> Int {
return when (type) {
"add" -> { a, b -> a + b }
"subtract" -> { a, b -> a - b }
else -> { _, _ -> 0 }
}
}
// Usage
println(square(5))
println(performOperation(10, 20, { x, y -> x * y }))
val add = getOperation("add")
println(add(5, 3))Scope functions (let, run, with, apply, also) provide a way to execute a block of code in the context of an object.
- let: Executes block on non-null object
- run: Executes block and returns result
- with: Calls functions on object without name
- apply: Configures object and returns it
- also: Performs additional operations
// Scope Functions in Kotlin
data class Person(var name: String, var age: Int, var city: String)
// let - execute block on non-null object
val person: Person? = Person("Alice", 25, "NYC")
person?.let {
println("Name: ${it.name}")
it.age = 26
}
// run - execute block and return result
val result = person?.run {
this.age = 27
"Updated age to $age"
}
println(result)
// with - calls functions on object without specifying name
val numbers = mutableListOf(1, 2, 3)
val sum = with(numbers) {
add(4)
add(5)
sum()
}
println("Sum: $sum")
// apply - configure object and return it
val updatedPerson = Person("Bob", 30, "LA").apply {
age = 31
city = "SF"
}
println(updatedPerson)
// also - perform additional operations
val numbers2 = mutableListOf(1, 2, 3)
numbers2.also {
println("Before: $it")
}.add(4)
println("After: $numbers2")
// takeIf - return object if condition true
val adult = person?.takeIf { it.age >= 18 }
println(adult)
// takeUnless - return object if condition false
val minor = person?.takeUnless { it.age >= 18 }
println(minor)Extension functions allow you to add new functionality to existing classes without modifying their source code.
- Declaration:
fun String.isEmail(): Boolean - Receiver:
thisrefers to the instance - Extension properties:
val String.wordCount - Generic extensions:
fun <T> List<T>.custom() - Null extensions:
fun String?.isNullOrBlank()
// Extension Functions in Kotlin
// Extension function for String
fun String.isEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
fun String.addPrefix(prefix: String): String {
return "$prefix$this"
}
// Extension function with receiver
fun Int.isEven(): Boolean = this % 2 == 0
fun Int.isOdd(): Boolean = this % 2 != 0
// Extension property
val String.wordCount: Int
get() = this.split(" ").size
// Extension function with generic
fun <T> List<T>.secondOrNull(): T? {
return if (this.size >= 2) this[1] else null
}
// Nullable extension
fun String?.isNullOrBlank(): Boolean {
return this == null || this.isBlank()
}
// Extension for List
fun <T> List<T>.customFilter(predicate: (T) -> Boolean): List<T> {
return this.filter(predicate)
}
// Usage
val email = "test@example.com"
println(email.isEmail()) // true
println("Hello".addPrefix("Greeting: "))
println(5.isEven()) // false
println("Hello World".wordCount) // 2
val list = listOf(1, 2, 3)
println(list.secondOrNull()) // 2Type aliases provide alternative names for existing types, improving readability and reducing repetition.
- Function types:
typealias Operation = (Int, Int) -> Int - Complex types:
typealias UserMap = Map<String, User> - Generic types: Can be used with generics
- Nested classes:
typealias InnerAlias = Inner - Readability: Makes code more readable
// Type Aliases in Kotlin
// Type alias for function type
typealias Operation = (Int, Int) -> Int
typealias StringPredicate = (String) -> Boolean
// Type alias for complex types
typealias UserMap = Map<String, User>
typealias ResultCallback = (Result<String>) -> Unit
// Usage
val add: Operation = { a, b -> a + b }
val multiply: Operation = { a, b -> a * b }
fun execute(op: Operation, a: Int, b: Int): Int {
return op(a, b)
}
typealias User = Pair<String, Int>
val users: UserMap = mapOf(
"user1" to Pair("Alice", 25),
"user2" to Pair("Bob", 30)
)
// Type alias for nested classes
class Outer {
inner class Inner
typealias InnerAlias = Inner
}
// Usage
println(execute(add, 5, 3))
println(execute(multiply, 5, 3))
println(users["user1"]?.first)Inline functions are functions that are expanded at the call site, reducing performance overhead of lambda expressions.
- Declaration:
inline fun measureTime(block: () -> Unit) - Crossinline: Prevents non-local returns
- Noinline: Prevents inlining of lambda
- Reified:
inline fun <reified T> isType() - Performance: Reduces overhead of lambdas
// Inline Functions in Kotlin
// Inline function to reduce overhead
inline fun measureTime(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
val end = System.currentTimeMillis()
println("Time: ${end - start}ms")
}
// Crossinline - prevents non-local returns
inline fun withCrossinline(crossinline block: () -> Unit) {
run {
block()
}
}
// Noinline - prevents inlining of lambda
inline fun withNoInline(noinline block: () -> Unit) {
block()
}
// Reified type parameter
inline fun <reified T> isType(value: Any): Boolean {
return value is T
}
// Usage
measureTime {
Thread.sleep(100)
}
class MyClass {
fun test() {
withCrossinline {
println("Crossinline")
}
}
}
println(isType<String>("Hello")) // true
println(isType<Int>("Hello")) // false
// Inline function with reified for type-safe casting
inline fun <reified T> List<*>.filterIsInstance(): List<T> {
return this.filter { it is T }.map { it as T }
}
val mixed = listOf(1, "Hello", 3.14, "World")
val strings = mixed.filterIsInstance<String>()
println(strings) // [Hello, World]Higher-order functions are functions that take other functions as parameters or return functions as results.
- Parameter:
fun operate(op: (Int, Int) -> Int) - Return:
fun getMultiplier(): (Int) -> Int - Function composition:
compose(f, g) - Callbacks: Used in event handling
- Functional programming: Core concept in FP
// Higher-Order Functions in Kotlin
// Function that takes a function as parameter
fun applyOperation(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
// Function that returns a function
fun getMultiplier(factor: Int): (Int) -> Int {
return { it * factor }
}
// Function composition
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}
// Higher-order function with multiple lambdas
fun process(
value: Int,
transform: (Int) -> Int,
filter: (Int) -> Boolean
): Int? {
return if (filter(value)) transform(value) else null
}
// Usage with lambda
val result = applyOperation(10, 20) { a, b -> a + b }
println(result)
val double = getMultiplier(2)
println(double(5)) // 10
val square = { x: Int -> x * x }
val addTen = { x: Int -> x + 10 }
val squareThenAddTen = compose(addTen, square)
println(squareThenAddTen(5)) // 35
// Using with named function
fun add(a: Int, b: Int) = a + b
val result2 = applyOperation(10, 20, ::add)
println(result2)Coroutines are lightweight threads for asynchronous programming in Kotlin. They provide structured concurrency without the overhead of threads.
- Lightweight: Lower overhead than threads
- Suspending:
suspend fun - Structured concurrency: Automatic cleanup
- Dispatchers:
Dispatchers.IO,Dispatchers.Default - Scopes:
runBlocking,coroutineScope
// Coroutines in Kotlin
import kotlinx.coroutines.*
// Basic coroutine
suspend fun fetchData(): String {
delay(1000) // Simulate network call
return "Data loaded"
}
// Launch coroutine
fun mainLaunch() = runBlocking {
val job = launch {
delay(2000)
println("Coroutine completed")
}
job.join()
}
// Async/await
fun mainAsync() = runBlocking {
val deferred = async {
fetchData()
}
val result = deferred.await()
println(result)
}
// Parallel coroutines
suspend fun parallelTasks() = coroutineScope {
val task1 = async { fetchData() }
val task2 = async { fetchData() }
val results = listOf(task1.await(), task2.await())
println("Results: $results")
}
// Coroutine with timeout
suspend fun withTimeoutExample() {
try {
withTimeout(1000) {
delay(2000)
}
} catch (e: TimeoutCancellationException) {
println("Timed out!")
}
}
// Structured concurrency
fun structuredConcurrency() = runBlocking {
coroutineScope {
launch {
delay(1000)
println("Task 1")
}
launch {
delay(500)
println("Task 2")
}
}
println("All tasks completed")
}Flows are cold streams of data that emit values over time. They are part of the coroutine library and support reactive programming.
- Cold streams: Emit values only when collected
- Operators:
filter,map,buffer - StateFlow: State holder with current value
- SharedFlow: Hot stream that can be shared
- Collect:
flow.collect { value -> }
// Flows in Kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
// Simple flow
fun simpleFlow(): Flow<Int> = flow {
for (i in 1..5) {
emit(i)
delay(100)
}
}
// Flow operators
fun processFlow() = runBlocking {
simpleFlow()
.filter { it % 2 == 0 }
.map { "Number: $it" }
.collect { println(it) }
}
// StateFlow
class StateFlowExample {
private val _state = MutableStateFlow(0)
val state: StateFlow<Int> = _state.asStateFlow()
fun increment() {
_state.value++
}
}
// SharedFlow
class SharedFlowExample {
private val _events = MutableSharedFlow<String>()
val events: SharedFlow<String> = _events.asSharedFlow()
suspend fun emitEvent(event: String) {
_events.emit(event)
}
}
// Flow with buffer
fun bufferedFlow() = flow {
for (i in 1..10) {
emit(i)
}
}.buffer()
// Flow with exception handling
fun safeFlow() = flow {
emit(1)
throw RuntimeException("Error")
}.catch { e ->
emit(-1)
println("Caught: ${e.message}")
}Channels are communication primitives for coroutines, allowing safe communication between different coroutines.
- Send/Receive:
channel.send(),channel.receive() - Buffered:
Channel(capacity = 10) - Produce:
produce { }builder - Actor:
actor { }for state management - Close:
channel.close()
// Channels in Kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
// Basic channel
fun basicChannel() = runBlocking {
val channel = Channel<String>()
launch {
channel.send("Hello")
channel.send("World")
channel.close()
}
for (msg in channel) {
println(msg)
}
}
// Buffered channel
fun bufferedChannel() = runBlocking {
val channel = Channel<Int>(capacity = 3)
launch {
for (i in 1..5) {
channel.send(i)
println("Sent: $i")
}
channel.close()
}
for (value in channel) {
println("Received: $value")
delay(100)
}
}
// Produce coroutine
fun produceExample() = runBlocking {
val numbers = produce {
for (i in 1..5) {
send(i)
delay(100)
}
}
for (num in numbers) {
println(num)
}
}
// Actor pattern
sealed class CounterMsg {
object Increment : CounterMsg()
object Decrement : CounterMsg()
class Get(val response: CompletableDeferred<Int>) : CounterMsg()
}
fun counterActor() = runBlocking {
val actor = actor<CounterMsg> {
var count = 0
for (msg in channel) {
when (msg) {
is CounterMsg.Increment -> count++
is CounterMsg.Decrement -> count--
is CounterMsg.Get -> msg.response.complete(count)
}
}
}
actor.send(CounterMsg.Increment)
actor.send(CounterMsg.Increment)
val response = CompletableDeferred<Int>()
actor.send(CounterMsg.Get(response))
println(response.await()) // 2
}Sealed classes and enum classes represent restricted hierarchies. Sealed classes are more flexible with multiple instances per type.
- Enum: Fixed set of constants
- Sealed: Restricted hierarchy
- Enum properties: Can have properties
- Sealed subclasses: Can be data classes
- When exhaustive: Both work with when expressions
// Sealed Classes and Enum Classes
// Enum class
enum class Color {
RED, GREEN, BLUE
}
enum class Status(val code: Int) {
SUCCESS(200),
ERROR(500),
LOADING(100)
}
// Sealed class for state management
sealed class UiState {
data class Success(val data: String) : UiState()
data class Error(val message: String) : UiState()
object Loading : UiState()
object Idle : UiState()
}
// Sealed class with properties
sealed class Payment {
data class Cash(val amount: Double) : Payment()
data class CreditCard(val number: String, val expiry: String) : Payment()
data class PayPal(val email: String) : Payment()
}
// Usage
fun handleState(state: UiState) {
when (state) {
is UiState.Success -> println("Data: ${state.data}")
is UiState.Error -> println("Error: ${state.message}")
UiState.Loading -> println("Loading...")
UiState.Idle -> println("Idle")
}
}
fun handlePayment(payment: Payment) {
when (payment) {
is Payment.Cash -> println("Cash amount: ${payment.amount}")
is Payment.CreditCard -> println("Card: ${payment.number}")
is Payment.PayPal -> println("PayPal: ${payment.email}")
}
}Generics allow you to write type-safe code that works with multiple types. They support variance, constraints, and reified types.
- Generic classes:
class Box<T>(val value: T) - Generic functions:
fun <T> swap(a: T, b: T) - Constraints:
<T : Number> - Variance:
out(covariant),in(contravariant) - Reified:
inline fun <reified T>
// Generics in Kotlin
// Generic class
class Box<T>(val value: T) {
fun getValue(): T = value
}
// Generic function
fun <T> swap(first: T, second: T): Pair<T, T> {
return Pair(second, first)
}
// Generic with constraints
fun <T : Number> sum(vararg items: T): Double {
return items.map { it.toDouble() }.sum()
}
// Generic with multiple constraints
fun <T> process(value: T) where T : CharSequence, T : Comparable<T> {
println("Length: ${value.length}")
println("First char: ${value[0]}")
}
// Variance - Covariant (out)
interface Producer<out T> {
fun produce(): T
}
// Contravariant (in)
interface Consumer<in T> {
fun consume(item: T)
}
// Invariant
interface Transformer<T> {
fun transform(input: T): T
}
// Usage
val box = Box("Hello")
println(box.getValue())
val (a, b) = swap(1, 2)
println("$a, $b")
println(sum(1, 2, 3, 4, 5))Delegation in Kotlin allows you to delegate implementation to another object or property. It's a powerful design pattern.
- Class delegation:
class C : A by delegate - Property delegation:
val lazy: T by lazy { } - Observable:
var name by Delegates.observable() - Vetoable: Validation before property change
- Custom delegates: Implement
ReadWriteProperty
// Delegation in Kotlin
// Class delegation
interface Repository {
fun getData(): String
fun saveData(data: String)
}
class DatabaseRepository : Repository {
override fun getData() = "Data from database"
override fun saveData(data: String) {
println("Saving to database: $data")
}
}
class CachedRepository(private val repo: Repository) : Repository by repo {
private var cache: String? = null
override fun getData(): String {
return cache ?: repo.getData().also { cache = it }
}
}
// Property delegation
class LazyProperty {
val expensiveValue: String by lazy {
println("Computing...")
"Result"
}
}
// Observable property
class ObservableProperty {
var name: String by Delegates.observable("Initial") { prop, old, new ->
println("$old -> $new")
}
}
// Vetoable property
class VetoableProperty {
var age: Int by Delegates.vetoable(0) { _, _, new ->
new >= 0
}
}
// Usage
val db = DatabaseRepository()
val cached = CachedRepository(db)
println(cached.getData()) // First call - from database
println(cached.getData()) // Second call - from cache
val lazyProp = LazyProperty()
println(lazyProp.expensiveValue) // Computes
println(lazyProp.expensiveValue) // Returns cachedObject declarations in Kotlin implement the singleton pattern. They ensure only one instance of the class exists.
- Singleton:
object AppConfig - Companion object: Class-level singleton
- Object expression: Anonymous objects
- Usage:
AppConfig.API_URL - Thread-safe: Initialization is thread-safe
// Object Declarations and Singletons
// Singleton pattern using object
object AppConfig {
const val API_URL = "https://api.example.com"
const val TIMEOUT = 5000
fun printConfig() {
println("API URL: $API_URL")
println("Timeout: $TIMEOUT")
}
}
// Companion object (singleton per class)
class UserManager {
companion object {
private var instance: UserManager? = null
fun getInstance(): UserManager {
if (instance == null) {
instance = UserManager()
}
return instance!!
}
}
fun login(username: String) {
println("User $username logged in")
}
}
// Object expression (anonymous object)
val greeting = object {
val message = "Hello"
fun print() {
println(message)
}
}
// Object expression implementing interface
interface ClickListener {
fun onClick()
}
val clickHandler = object : ClickListener {
override fun onClick() {
println("Clicked!")
}
}
// Usage
println(AppConfig.API_URL)
AppConfig.printConfig()
val manager = UserManager.getInstance()
manager.login("Alice")
greeting.print()
clickHandler.onClick()Kotlin's features like lambdas, extension functions, and infix notation allow creating type-safe DSLs.
- Builder pattern: Configure objects with lambdas
- Infx functions:
infix fun - Lambda with receiver:
T.() -> Unit - Scope functions:
apply,run - Type-safe builders: HTML, XML, Gradle
// DSL (Domain Specific Language) in Kotlin
// HTML DSL example
class HTML {
fun body(block: Body.() -> Unit) {
val body = Body()
body.block()
println(body.render())
}
}
class Body {
private val elements = mutableListOf<String>()
fun h1(text: String) {
elements.add("<h1>$text</h1>")
}
fun p(text: String) {
elements.add("<p>$text</p>")
}
fun render(): String {
return "<body>${elements.joinToString("")}</body>"
}
}
// Builder DSL
class UserBuilder {
var name: String = ""
var age: Int = 0
var email: String = ""
fun build(): User = User(name, age, email)
}
data class User(val name: String, val age: Int, val email: String)
fun user(block: UserBuilder.() -> Unit): User {
val builder = UserBuilder()
builder.block()
return builder.build()
}
// Usage
fun html(block: HTML.() -> Unit) {
val html = HTML()
html.block()
}
// DSL usage
html {
body {
h1("Welcome to Kotlin DSL")
p("This is a paragraph")
p("Another paragraph")
}
}
// Builder usage
val user = user {
name = "Alice"
age = 25
email = "alice@example.com"
}
println(user)Annotations are metadata attached to code elements. They provide information to the compiler or runtime.
- Custom annotations:
@annotation class MyAnnotation - Targets:
@Target(CLASS, FUNCTION, PROPERTY) - Retention:
@Retention(SOURCE, BINARY, RUNTIME) - Repeatable:
@Repeatable - Usage:
@MyAnnotation("value")
// Annotations in Kotlin
// Custom annotation
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class MyAnnotation(val value: String = "default")
// Annotation with multiple parameters
@Target(AnnotationTarget.PROPERTY)
annotation class FieldInfo(val name: String, val required: Boolean = false)
// Use annotation
@MyAnnotation("test")
class AnnotatedClass {
@FieldInfo(name = "username", required = true)
var username: String = ""
@MyAnnotation("method")
fun annotatedMethod() {
println("Annotated method")
}
}
// Reflection to read annotations
fun readAnnotations() {
val clazz = AnnotatedClass::class
clazz.annotations.forEach { annotation ->
println("Class annotation: $annotation")
}
val methods = clazz.declaredFunctions
methods.forEach { method ->
method.annotations.forEach { annotation ->
println("Method annotation: $annotation")
}
}
}
// Repeatable annotations
@Repeatable
annotation class Permission(val value: String)
@Permission("read")
@Permission("write")
class SecureClass
// Usage
val annotated = AnnotatedClass()
annotated.annotatedMethod()
readAnnotations()Reflection allows inspecting classes, properties, and functions at runtime. Kotlin provides both Java and Kotlin reflection.
- KClass:
Person::class - Properties:
kClass.declaredMemberProperties - Functions:
kClass.declaredFunctions - Call:
function.call(obj, args) - Constructors:
kClass.primaryConstructor
// Reflection in Kotlin
import kotlin.reflect.*
import kotlin.reflect.full.*
// Class for reflection examples
data class Person(
val name: String,
var age: Int,
val city: String = "Unknown"
) {
fun greet(): String {
return "Hello, my name is $name"
}
fun updateAge(newAge: Int) {
age = newAge
}
}
// Basic reflection
fun basicReflection() {
val person = Person("Alice", 25)
val kClass = person::class
println("Class name: ${kClass.simpleName}")
println("Members: ${kClass.members}")
println("Properties: ${kClass.declaredMemberProperties}")
}
// Accessing properties
fun accessProperties() {
val person = Person("Alice", 25)
val kClass = person::class
kClass.declaredMemberProperties.forEach { prop ->
println("${prop.name} = ${prop.get(person)}")
}
}
// Calling functions
fun callFunctions() {
val person = Person("Alice", 25)
val kClass = person::class
val greetFunc = kClass.declaredFunctions.find { it.name == "greet" }
greetFunc?.call(person)?.let { println(it) }
val updateFunc = kClass.declaredFunctions.find { it.name == "updateAge" }
updateFunc?.call(person, 30)
println("Updated age: ${person.age}")
}
// Create instance
fun createInstance() {
val kClass = Person::class
val constructor = kClass.primaryConstructor
val person = constructor?.call("Bob", 30, "NYC")
println(person)
}Coroutine contexts define the environment in which coroutines run. Dispatchers determine which thread pool is used.
- Dispatchers.Default: CPU-intensive work
- Dispatchers.IO: I/O operations
- Dispatchers.Main: Main thread (Android/UI)
- Dispatchers.Unconfined: Not confined to any thread
- Custom context:
Dispatchers.IO + CoroutineName("MyName")
// Coroutine Context and Dispatchers
import kotlinx.coroutines.*
// Different dispatchers
fun dispatcherExample() = runBlocking {
launch(Dispatchers.Default) {
println("Default: ${Thread.currentThread().name}")
}
launch(Dispatchers.IO) {
println("IO: ${Thread.currentThread().name}")
}
launch(Dispatchers.Main) {
println("Main: ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("Unconfined: ${Thread.currentThread().name}")
}
}
// Custom context
fun customContext() = runBlocking {
val context = Dispatchers.IO + CoroutineName("Custom")
launch(context) {
println("Context: ${coroutineContext[CoroutineName]}")
}
}
// ThreadLocal with coroutines
val threadLocal = ThreadLocal<String>()
fun threadLocalExample() = runBlocking {
threadLocal.set("Main")
withContext(Dispatchers.IO) {
threadLocal.set("IO")
println("ThreadLocal: ${threadLocal.get()}")
}
println("ThreadLocal restored: ${threadLocal.get()}")
}
// Supervisor job
fun supervisorExample() = runBlocking {
val supervisor = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + supervisor)
scope.launch {
delay(100)
throw RuntimeException("Error")
}
scope.launch {
delay(200)
println("Still running")
}
delay(500)
}
// Coroutine exception handler
fun exceptionHandlerExample() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught exception: ${exception.message}")
}
val scope = CoroutineScope(Dispatchers.IO + handler)
scope.launch {
throw RuntimeException("Test exception")
}
delay(100)
}Flow operators allow transforming and processing streams of data. They support filtering, mapping, buffering, and flattening.
- filter: Select specific values
- map: Transform values
- buffer: Buffer emissions
- conflate: Drop intermediate values
- collectLatest: Cancel previous collection
// Flow Operators and Transformations
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
// Basic flow transformation
fun flowTransformExample() = runBlocking {
(1..10).asFlow()
.filter { it % 2 == 0 }
.map { "Number $it" }
.collect { println(it) }
}
// Flow with buffer
fun flowBufferExample() = runBlocking {
flow {
for (i in 1..5) {
emit(i)
delay(100)
}
}
.buffer()
.collect { println(it) }
}
// Flow with conflate
fun flowConflateExample() = runBlocking {
flow {
for (i in 1..10) {
emit(i)
delay(50)
}
}
.conflate()
.collect {
delay(100)
println(it)
}
}
// Flow with collectLatest
fun flowCollectLatestExample() = runBlocking {
flow {
for (i in 1..10) {
emit(i)
delay(50)
}
}
.collectLatest { value ->
println("Processing $value")
delay(100)
println("Done $value")
}
}
// FlatMapConcat, FlatMapMerge, FlatMapLatest
fun flowFlatMapExample() = runBlocking {
flow {
emit(1)
emit(2)
emit(3)
}
.flatMapConcat { value ->
flow {
emit("$value-a")
emit("$value-b")
}
}
.collect { println(it) }
}Coroutine scopes define the lifecycle of coroutines. They provide structured concurrency and automatic cleanup.
- CoroutineScope: Interface for scopes
- lifecycleScope: Android lifecycle scopes
- viewModelScope: ViewModel scopes
- GlobalScope: Application-level scope
- SupervisorJob: Independent job hierarchy
// Coroutine Scopes and Lifecycle
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
// Custom scope with job
class MyScope : CoroutineScope {
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext = Dispatchers.IO + job
fun cancel() {
job.cancel()
}
}
// Lifecycle-aware scope (Android-like)
class LifecycleScope {
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job {
return scope.launch(block = block)
}
fun onDestroy() {
scope.cancel()
}
}
// GlobalScope vs CoroutineScope
fun scopeComparison() = runBlocking {
// GlobalScope - runs until complete
GlobalScope.launch {
delay(1000)
println("GlobalScope")
}
// CoroutineScope - tied to parent
coroutineScope {
launch {
delay(1000)
println("CoroutineScope")
}
}
}
// Scope with timeout
fun timeoutScope() = runBlocking {
try {
withTimeout(1000) {
delay(2000)
}
} catch (e: TimeoutCancellationException) {
println("Timed out")
}
// With timeout or null
val result = withTimeoutOrNull(1000) {
delay(500)
"Success"
}
println(result ?: "Timed out")
}Coroutine exception handling uses try-catch, CoroutineExceptionHandler, and supervisor scopes for error isolation.
- Try-catch:
try catch (e: Exception) - ExceptionHandler:
CoroutineExceptionHandler - SupervisorJob: Isolates child failures
- Flow catch:
.catch - SupervisorScope:
supervisorScope
// Coroutine Exception Handling
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
// Try-catch in coroutine
fun tryCatchExample() = runBlocking {
try {
launch {
throw RuntimeException("Error")
}
} catch (e: Exception) {
println("Caught: ${e.message}")
}
}
// CoroutineExceptionHandler
fun exceptionHandler() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("Handler caught: ${exception.message}")
}
val scope = CoroutineScope(Dispatchers.IO + handler)
scope.launch {
throw RuntimeException("Test")
}
delay(100)
}
// Supervisor job for child isolation
fun supervisorExample2() = runBlocking {
val supervisor = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + supervisor)
scope.launch {
try {
delay(100)
throw RuntimeException("Child 1 error")
} catch (e: Exception) {
println("Child 1 caught: ${e.message}")
}
}
scope.launch {
delay(200)
println("Child 2 still running")
}
delay(500)
}
// Flow exception handling
fun flowException() = runBlocking {
flow {
emit(1)
throw RuntimeException("Flow error")
}
.catch { e ->
println("Flow caught: ${e.message}")
emit(-1)
}
.collect { println(it) }
}
// SupervisorScope
fun supervisorScopeExample() = runBlocking {
supervisorScope {
launch {
throw RuntimeException("Error")
}
launch {
delay(100)
println("Still running")
}
}
}Channel patterns include producer-consumer, fan-out, and fan-in. These enable communication between coroutines.
- Producer-consumer: Single producer, single consumer
- Fan-out: Multiple consumers from one channel
- Fan-in: Multiple producers to one channel
- Pipelines: Chaining producers and consumers
- Buffer: Channel capacity for buffering
// Channel Producers and Consumer Patterns
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
// Producer-Consumer pattern
fun producerConsumer() = runBlocking {
val channel = Channel<Int>(capacity = 10)
// Producer
val producer = launch {
for (i in 1..20) {
channel.send(i)
println("Produced: $i")
delay(100)
}
channel.close()
}
// Consumer
val consumer = launch {
for (value in channel) {
println("Consumed: $value")
delay(150)
}
}
producer.join()
consumer.join()
}
// Fan-out pattern
fun fanOutExample() = runBlocking {
val channel = Channel<Int>(capacity = 10)
// Producer
launch {
for (i in 1..20) {
channel.send(i)
}
channel.close()
}
// Multiple consumers
repeat(3) { id ->
launch {
for (value in channel) {
println("Consumer $id: $value")
delay(100)
}
}
}
}
// Fan-in pattern
fun fanInExample() = runBlocking {
val channel = Channel<String>(capacity = 10)
// Multiple producers
repeat(3) { id ->
launch {
for (i in 1..5) {
channel.send("Producer $id: $i")
delay(50)
}
}
}
// Single consumer
repeat(15) {
println(channel.receive())
}
channel.close()
}Coroutine cancellation is cooperative. You need to check isActive, use cancellation-aware functions, and handle cleanup.
- Check isActive:
if (!isActive) break - Cancel:
job.cancel() - Finally:
finallyfor cleanup - NonCancellable:
withContext(NonCancellable) - Timeout:
withTimeout(1000)
// Coroutine Cancellation
import kotlinx.coroutines.*
// Cooperative cancellation
fun cooperativeCancellation() = runBlocking {
val job = launch {
var i = 0
while (i < 100) {
if (!isActive) break
println("Working: $i")
i++
delay(50)
}
}
delay(200)
job.cancel()
}
// Cancellation with finally
fun cancellationFinally() = runBlocking {
val job = launch {
try {
repeat(100) { i ->
println("Processing: $i")
delay(100)
}
} finally {
println("Cleaning up")
// Non-cancellable block
withContext(NonCancellable) {
delay(100)
println("Cleanup done")
}
}
}
delay(250)
job.cancel()
}
// Cancellation with timeout
fun cancellationTimeout() = runBlocking {
try {
withTimeout(1000) {
repeat(10) { i ->
delay(200)
println("Iteration: $i")
}
}
} catch (e: TimeoutCancellationException) {
println("Timed out")
}
}
// Custom cancellation check
fun customCancellation() = runBlocking {
val job = launch {
var i = 0
while (i < 1000 && isActive) {
if (i % 100 == 0) {
println("Still running: $i")
}
i++
Thread.sleep(1) // CPU-bound work
}
}
delay(100)
job.cancel()
job.join()
}Kotlin provides kotlinx-coroutines-test for testing coroutines. It offers test dispatchers and virtual time control.
- runTest:
runTestfor test execution - advanceTimeBy: Virtual time advancement
- advanceUntilIdle: Run all pending tasks
- TestDispatcher: Controlled dispatcher
- Assertions: Standard test assertions
// Testing Coroutines
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
import kotlin.test.*
// Test dispatcher
class CoroutineTest {
@Test
fun testCoroutine() = runTest {
val result = async {
delay(1000)
"Success"
}.await()
assertEquals("Success", result)
}
@Test
fun testWithDelay() = runTest {
var result = ""
launch {
delay(1000)
result = "Done"
}
advanceTimeBy(1000)
assertEquals("Done", result)
}
@Test
fun testMultipleCoroutines() = runTest {
val results = mutableListOf<String>()
launch {
delay(500)
results.add("Task 1")
}
launch {
delay(300)
results.add("Task 2")
}
advanceUntilIdle()
assertEquals(listOf("Task 2", "Task 1"), results)
}
@Test
fun testFlow() = runTest {
val flow = flow {
emit(1)
delay(500)
emit(2)
}
val result = flow.toList()
assertEquals(listOf(1, 2), result)
}
}
// Test with time control
fun timeControlTest() = runTest {
var counter = 0
launch {
while (true) {
delay(1000)
counter++
}
}
advanceTimeBy(3000)
assertEquals(3, counter)
}Kotlin Multiplatform allows sharing code between different platforms (JVM, JS, Native). It's used for cross-platform development.
- Common code: Shared business logic
- Platform-specific: Actual implementations
- Expect/Actual:
expectandactual - Serialization: Shared serialization logic
- Coroutines: Shared async code
// Kotlin Multiplatform
// Common code
expect fun platformName(): String
fun greet(): String {
return "Hello from ${platformName()}"
}
// Platform-specific implementations (Android)
// actual fun platformName(): String = "Android"
// Platform-specific implementations (iOS)
// actual fun platformName(): String = "iOS"
// Platform-specific implementations (JS)
// actual fun platformName(): String = "JavaScript"
// Common expect class
expect class Platform() {
fun getVersion(): String
}
// Common class
class PlatformInfo {
fun getInfo(): String {
return "${greet()} version ${Platform().getVersion()}"
}
}
// Multiplatform with coroutines
expect suspend fun fetchData(): String
fun processData() = runBlocking {
val data = fetchData()
println("Data: $data")
}
// Multiplatform with serialization
@Serializable
data class User(
val id: Int,
val name: String,
val email: String
)
expect fun encodeUser(user: User): String
expect fun decodeUser(data: String): UserReverse a string using the built-in reversed() function or manually.
- Built-in:
str.reversed() - StringBuilder:
StringBuilder(str).reverse() - Manual: Iterate from end to start
- Complexity: O(n) time
// Reverse a string
fun reverseString(str: String): String {
return str.reversed()
}
println(reverseString("hello")) // "olleh"
// Using StringBuilder
fun reverseStringBuilder(str: String): String {
return StringBuilder(str).reverse().toString()
}Check if a string is a palindrome using reversed() or two-pointer approach.
- Method:
cleaned == cleaned.reversed() - Two-pointer: Compare from both ends
- Case insensitive:
lowercase() - Ignore non-alphanumeric:
replace(Regex("[^a-z0-9]"), "")
// Check palindrome
fun isPalindrome(str: String): Boolean {
val cleaned = str.lowercase().replace(Regex("[^a-z0-9]"), "")
return cleaned == cleaned.reversed()
}
println(isPalindrome("racecar")) // true
println(isPalindrome("hello")) // false
// Two-pointer approach
fun isPalindromeTwoPointer(str: String): Boolean {
val cleaned = str.lowercase().replace(Regex("[^a-z0-9]"), "")
var left = 0
var right = cleaned.length - 1
while (left < right) {
if (cleaned[left] != cleaned[right]) return false
left++
right--
}
return true
}Find maximum value in an array using maxOrNull() or manual iteration.
- Built-in:
arr.maxOrNull() - Manual: Iterate and track max
- Empty array: Handle with
require - Complexity: O(n) time
// Find max in array
fun findMax(arr: IntArray): Int {
return arr.maxOrNull() ?: throw IllegalArgumentException("Empty array")
}
println(findMax(intArrayOf(1, 5, 3, 9, 2))) // 9
// Manual implementation
fun findMaxManual(arr: IntArray): Int {
var maxVal = arr[0]
for (num in arr) {
if (num > maxVal) maxVal = num
}
return maxVal
}Remove duplicates from an array using distinct() or toSet().
- distinct():
arr.distinct() - Set:
arr.toSet().toList() - Order:
distinct()preserves order - Complexity: O(n) time
// Remove duplicates
fun removeDuplicates(arr: List<Int>): List<Int> {
return arr.distinct()
}
println(removeDuplicates(listOf(1, 2, 2, 3, 3, 4))) // [1, 2, 3, 4]
// Using Set
fun removeDuplicatesSet(arr: List<Int>): List<Int> {
return arr.toSet().toList()
}Merge two arrays using + operator or plus() function.
- Operator:
arr1 + arr2 - plus():
arr1.plus(arr2) - Mutable:
arr1.addAll(arr2) - Unique:
union()for unique merge
// Merge arrays
fun mergeArrays(arr1: IntArray, arr2: IntArray): IntArray {
return arr1 + arr2
}
println(mergeArrays(intArrayOf(1, 2), intArrayOf(3, 4)).joinToString()) // [1, 2, 3, 4]
// Alternative
fun mergeArraysPlus(arr1: IntArray, arr2: IntArray): IntArray {
return arr1.plus(arr2)
}Convert string to number using toInt() or toIntOrNull() for safe conversion.
- toInt():
str.toInt() - toIntOrNull():
str.toIntOrNull() - toDouble():
str.toDouble() - Error handling: Use
toIntOrNull()
// Convert string to number
fun stringToNumber(str: String): Int {
return str.toInt()
}
println(stringToNumber("42")) // 42
// Safe conversion
fun stringToNumberSafe(str: String): Int? {
return str.toIntOrNull()
}Iterate through a map using for loop with destructuring.
- Destructuring:
for ((key, value) in map) - forEach:
map.forEach { (key, value) -> } - Keys:
map.keys - Values:
map.values
// Loop through map
fun loopMap(map: Map<String, Any>) {
for ((key, value) in map) {
println("$key => $value")
}
}
// Alternative
fun loopMapAlternate(map: Map<String, Any>) {
map.forEach { (key, value) ->
println("$key => $value")
}
}
val data = mapOf("name" to "Alice", "age" to 25, "city" to "NYC")
loopMap(data)Delay function execution using delay() from coroutines or Thread.sleep().
- Coroutine:
suspend fun delay(ms, block) - Thread:
Thread.sleep(ms) - Async:
CoroutineScopewith launch - Timer:
Timer().schedule()
// Delay function execution
import kotlinx.coroutines.*
suspend fun delayedExecution(delayMs: Long, block: () -> Unit) {
delay(delayMs)
block()
}
// Example usage
suspend fun main() {
delayedExecution(2000) {
println("After 2 seconds")
}
}
// Using Thread.sleep (blocking)
fun delayedExecutionBlocking(delayMs: Long, block: () -> Unit) {
Thread.sleep(delayMs)
block()
}Make HTTP GET requests using Java's HttpURLConnection or Ktor client.
- Java:
HttpURLConnection - Ktor:
HttpClient().get(url) - Retrofit: Android HTTP client
- OkHttp: Popular HTTP client
// HTTP GET request
import java.net.HttpURLConnection
import java.net.URL
fun fetchData(urlString: String): String {
val url = URL(urlString)
val connection = url.openConnection() as HttpURLConnection
return try {
connection.inputStream.bufferedReader().readText()
} finally {
connection.disconnect()
}
}
// Using Ktor client (coroutines)
suspend fun fetchDataKtor(url: String): String {
val client = HttpClient()
return client.get(url).bodyAsText()
}
// Example
// val data = fetchData("https://api.example.com/data")Create a Deferred using async coroutine builder.
- async:
async - await:
deferred.await() - Error handling:
try-catch - Chaining: Combine multiple deferreds
// Create a promise-like Deferred
import kotlinx.coroutines.*
suspend fun createDeferred(shouldResolve: Boolean): Deferred<String> {
return GlobalScope.async {
delay(1000)
if (shouldResolve) {
"Success!"
} else {
throw Exception("Failed!")
}
}
}
// Usage
suspend fun main() {
val deferred = createDeferred(true)
try {
val result = deferred.await()
println(result)
} catch (e: Exception) {
println("Caught: ${e.message}")
}
}Calculate factorial using recursion or iteration.
- Recursive:
if (n <= 1) 1 else n * factorial(n-1) - Iterative: Loop from 2 to n
- Edge cases: 0! = 1, handle negatives
- Performance: Iterative is faster
// Factorial
fun factorial(n: Int): Long {
return if (n <= 1) 1 else n.toLong() * factorial(n - 1)
}
println(factorial(5)) // 120
// Iterative version
fun factorialIterative(n: Int): Long {
var result = 1L
for (i in 2..n) {
result *= i
}
return result
}Calculate Fibonacci numbers using recursion, iteration, or memoization.
- Recursive:
if (n <= 1) n else fib(n-1) + fib(n-2) - Iterative: Loop with variables
- Memoization: Cache results
- Complexity: O(2^n) recursive, O(n) iterative
// Fibonacci
fun fibonacci(n: Int): Int {
return if (n <= 1) n else fibonacci(n - 1) + fibonacci(n - 2)
}
println(fibonacci(8)) // 21
// Iterative version
fun fibonacciIterative(n: Int): Int {
var a = 0
var b = 1
for (i in 2..n) {
val temp = a + b
a = b
b = temp
}
return if (n > 0) b else a
}FizzBuzz prints numbers, replacing multiples of 3 with "Fizz", 5 with "Buzz", and 15 with "FizzBuzz".
- When expression:
when { i % 15 == 0 -> } - Order: Check 15 first
- Range:
for (i in 1..n) - Common interview: Frequently asked
// FizzBuzz
fun fizzBuzz(n: Int) {
for (i in 1..n) {
when {
i % 15 == 0 -> println("FizzBuzz")
i % 3 == 0 -> println("Fizz")
i % 5 == 0 -> println("Buzz")
else -> println(i)
}
}
}
fizzBuzz(15)Find missing number using formula n*(n+1)/2 - sum.
- Formula:
total - sum(arr) - XOR method: XOR all numbers and indices
- Edge cases: Empty array, missing first or last
- Complexity: O(n) time, O(1) space
// Find missing number
fun findMissing(arr: IntArray): Int {
val n = arr.size + 1
val total = n * (n + 1) / 2
val sum = arr.sum()
return total - sum
}
println(findMissing(intArrayOf(1, 2, 4, 5, 6))) // 3Find duplicates using a Set to track seen elements.
- Set method: Track seen elements
- Filter method:
arr.filter { it in seen } - Group by:
arr.groupBy { it }.filter { it.value.size > 1 } - Complexity: O(n) time
// Find duplicates
fun findDuplicates(arr: List<Int>): Set<Int> {
val seen = mutableSetOf<Int>()
val duplicates = mutableSetOf<Int>()
for (item in arr) {
if (item in seen) {
duplicates.add(item)
} else {
seen.add(item)
}
}
return duplicates
}
println(findDuplicates(listOf(1, 2, 3, 2, 4, 3))) // [2, 3]Calculate sum using sum() or manual iteration.
- Built-in:
arr.sum() - Manual:
var sum = 0; for (num in arr) sum += num - Reduce:
arr.reduce { acc, i -> acc + i } - Empty array: Returns 0
// Sum of array
fun sumArray(arr: IntArray): Int {
return arr.sum()
}
println(sumArray(intArrayOf(1, 2, 3, 4, 5))) // 15
// Manual implementation
fun sumArrayManual(arr: IntArray): Int {
var sum = 0
for (num in arr) {
sum += num
}
return sum
}Calculate average using average() or manual division.
- Built-in:
arr.average() - Manual:
arr.sum() / arr.size - Empty array: Handle with if check
- Precision: Returns Double
// Average of array
fun averageArray(arr: IntArray): Double {
return arr.average()
}
println(averageArray(intArrayOf(1, 2, 3, 4, 5))) // 3.0
// Manual implementation
fun averageArrayManual(arr: IntArray): Double {
return arr.sum().toDouble() / arr.size
}Sort using sorted() or sort() for in-place sorting.
- Non-mutating:
arr.sorted() - Mutating:
arr.sort() - Custom:
arr.sortedBy { it } - Complexity: O(n log n)
// Sort array ascending
fun sortAscending(arr: IntArray): IntArray {
return arr.sortedArray()
}
println(sortAscending(intArrayOf(5, 2, 8, 1, 9)).joinToString()) // [1, 2, 5, 8, 9]
// In-place sorting
fun sortAscendingInPlace(arr: IntArray) {
arr.sort()
}Sort descending using sortedDescending() or sortDescending().
- Non-mutating:
arr.sortedDescending() - Mutating:
arr.sortDescending() - Custom:
arr.sortedBy { -it } - Complexity: O(n log n)
// Sort array descending
fun sortDescending(arr: IntArray): IntArray {
return arr.sortedDescending().toIntArray()
}
println(sortDescending(intArrayOf(5, 2, 8, 1, 9)).joinToString()) // [9, 8, 5, 2, 1]
// In-place sorting
fun sortDescendingInPlace(arr: IntArray) {
arr.sortDescending()
}Flatten a nested array using recursion or flatMap.
- Recursive: Check if element is list
- flatMap:
arr.flatMap { if (it is List<*>) flatten(it) else listOf(it) } - Depth: Handle multiple nesting levels
- Complexity: O(n) time
// Flatten nested array
fun flattenArray(arr: List<Any>): List<Any> {
val result = mutableListOf<Any>()
for (item in arr) {
if (item is List<*>) {
result.addAll(flattenArray(item as List<Any>))
} else {
result.add(item)
}
}
return result
}
println(flattenArray(listOf(1, listOf(2, listOf(3, 4), 5), 6))) // [1, 2, 3, 4, 5, 6]
// Using Kotlin's flatMap
fun flattenArrayFlat(arr: List<Any>): List<Any> {
return arr.flatMap {
if (it is List<*>) flattenArrayFlat(it as List<Any>) else listOf(it)
}
}Split array into chunks using chunked() or manual slicing.
- Built-in:
arr.chunked(size) - Manual:
arr.slice(i until min(i + size, arr.size)) - Step:
for (i in arr.indices step size) - Use case: Batch processing
// Chunk array
fun chunkArray(arr: List<Int>, size: Int): List<List<Int>> {
return arr.chunked(size)
}
println(chunkArray(listOf(1, 2, 3, 4, 5, 6), 2)) // [[1, 2], [3, 4], [5, 6]]
// Manual implementation
fun chunkArrayManual(arr: List<Int>, size: Int): List<List<Int>> {
val result = mutableListOf<List<Int>>()
for (i in arr.indices step size) {
result.add(arr.slice(i until minOf(i + size, arr.size)))
}
return result
}Binary search on sorted array using binarySearch() or manual implementation.
- Built-in:
arr.binarySearch(target) - Manual: While loop with left/right pointers
- Time: O(log n)
- Requirement: Array must be sorted
// Binary search
fun binarySearch(arr: IntArray, target: Int): Int {
var left = 0
var right = arr.size - 1
while (left <= right) {
val mid = (left + right) / 2
when {
arr[mid] == target -> return mid
arr[mid] < target -> left = mid + 1
else -> right = mid - 1
}
}
return -1
}
println(binarySearch(intArrayOf(1, 2, 3, 4, 5, 6, 7), 5)) // 4
// Using built-in
fun binarySearchBuiltIn(arr: IntArray, target: Int): Int {
return arr.binarySearch(target)
}Quick sort using pivot-based partitioning and recursion.
- Algorithm: Choose pivot, partition, recurse
- Time: O(n log n) average, O(n²) worst
- In-place: Implement in-place for performance
- Pivot: First, last, or random
// Quick sort
fun quickSort(arr: IntArray): IntArray {
if (arr.size <= 1) return arr
val pivot = arr[0]
val left = arr.sliceArray(1 until arr.size).filter { it < pivot }.toIntArray()
val right = arr.sliceArray(1 until arr.size).filter { it >= pivot }.toIntArray()
return quickSort(left) + pivot + quickSort(right)
}
println(quickSort(intArrayOf(5, 3, 8, 4, 2, 7, 1, 6)).joinToString())
// In-place quick sort
fun quickSortInPlace(arr: IntArray, low: Int = 0, high: Int = arr.size - 1) {
if (low < high) {
val pi = partition(arr, low, high)
quickSortInPlace(arr, low, pi - 1)
quickSortInPlace(arr, pi + 1, high)
}
}
fun partition(arr: IntArray, low: Int, high: Int): Int {
val pivot = arr[high]
var i = low - 1
for (j in low until high) {
if (arr[j] <= pivot) {
i++
arr[i] = arr[j].also { arr[j] = arr[i] }
}
}
arr[i + 1] = arr[high].also { arr[high] = arr[i + 1] }
return i + 1
}Merge sort using divide-and-conquer and merging.
- Algorithm: Divide, sort, merge
- Time: O(n log n)
- Stable: Maintains relative order
- Space: O(n) auxiliary space
// Merge sort
fun mergeSort(arr: IntArray): IntArray {
if (arr.size <= 1) return arr
val mid = arr.size / 2
val left = mergeSort(arr.sliceArray(0 until mid))
val right = mergeSort(arr.sliceArray(mid until arr.size))
return merge(left, right)
}
fun merge(left: IntArray, right: IntArray): IntArray {
var i = 0
var j = 0
val result = mutableListOf<Int>()
while (i < left.size && j < right.size) {
if (left[i] <= right[j]) {
result.add(left[i++])
} else {
result.add(right[j++])
}
}
result.addAll(left.sliceArray(i until left.size))
result.addAll(right.sliceArray(j until right.size))
return result.toIntArray()
}Bubble sort with early termination optimization.
- Algorithm: Compare adjacent, swap if needed
- Time: O(n²) worst case
- Optimization: Stop if no swaps
- Use case: Educational, small datasets
// Bubble sort
fun bubbleSort(arr: IntArray): IntArray {
val sorted = arr.copyOf()
for (i in 0 until sorted.size - 1) {
for (j in 0 until sorted.size - 1 - i) {
if (sorted[j] > sorted[j + 1]) {
val temp = sorted[j]
sorted[j] = sorted[j + 1]
sorted[j + 1] = temp
}
}
}
return sorted
}
// Optimized bubble sort
fun bubbleSortOptimized(arr: IntArray): IntArray {
val sorted = arr.copyOf()
for (i in 0 until sorted.size - 1) {
var swapped = false
for (j in 0 until sorted.size - 1 - i) {
if (sorted[j] > sorted[j + 1]) {
val temp = sorted[j]
sorted[j] = sorted[j + 1]
sorted[j + 1] = temp
swapped = true
}
}
if (!swapped) break
}
return sorted
}Find common elements using filter or intersect.
- Filter:
arr1.filter { it in arr2 } - Intersect:
arr1.intersect(arr2) - Set:
arr1.filter { it in set2 } - Complexity: O(n²) with in, O(n) with Set
// Intersection of arrays
fun intersection(arr1: IntArray, arr2: IntArray): IntArray {
return arr1.filter { it in arr2 }.toIntArray()
}
println(intersection(intArrayOf(1, 2, 3, 4), intArrayOf(3, 4, 5, 6)).joinToString()) // [3, 4]
// Using Set for efficiency
fun intersectionSet(arr1: IntArray, arr2: IntArray): IntArray {
val set2 = arr2.toSet()
return arr1.filter { it in set2 }.toIntArray()
}Combine arrays with unique elements using union or Set.
- Union:
arr1.union(arr2) - Distinct:
(arr1 + arr2).distinct() - Set:
arr1.toSet() + arr2.toSet() - Complexity: O(n) time
// Union of arrays
fun union(arr1: IntArray, arr2: IntArray): IntArray {
return (arr1 + arr2).distinct().toIntArray()
}
println(union(intArrayOf(1, 2, 3), intArrayOf(3, 4, 5)).joinToString()) // [1, 2, 3, 4, 5]
// Using Set
fun unionSet(arr1: IntArray, arr2: IntArray): IntArray {
return (arr1.toSet() + arr2.toSet()).toIntArray()
}Find elements in first array not in second using filter.
- Difference:
arr1.filter { it !in arr2 } - Symmetric:
arr1.subtract(arr2) + arr2.subtract(arr1) - Set: Use Set for efficiency
- Complexity: O(n²) with in, O(n) with Set
// Difference of arrays
fun difference(arr1: IntArray, arr2: IntArray): IntArray {
return arr1.filter { it !in arr2 }.toIntArray()
}
println(difference(intArrayOf(1, 2, 3, 4), intArrayOf(3, 4, 5, 6)).joinToString()) // [1, 2]
// Symmetric difference
fun symmetricDifference(arr1: IntArray, arr2: IntArray): IntArray {
return (arr1.filter { it !in arr2 } + arr2.filter { it !in arr1 }).toIntArray()
}Group objects by property using groupBy.
- Built-in:
items.groupBy { it.type } - Custom: Manual grouping with map
- Multiple keys: Group by multiple properties
- Complexity: O(n) time
// Group by property
data class Item(val type: String, val name: String)
fun groupByProperty(items: List<Item>, key: String): Map<String, List<Item>> {
return items.groupBy {
when (key) {
"type" -> it.type
else -> it.name
}
}
}
val data = listOf(
Item("fruit", "apple"),
Item("fruit", "banana"),
Item("veg", "carrot")
)
println(groupByProperty(data, "type"))Deep clone objects by recursively copying all nested structures.
- Method: Recursive copying
- Maps: Copy entries recursively
- Lists: Copy elements recursively
- Limitations: May not handle all edge cases
// Deep clone object
fun <T> deepClone(obj: T): T {
return try {
@Suppress("UNCHECKED_CAST")
when (obj) {
is Map<*, *> -> {
val map = mutableMapOf<Any, Any?>()
obj.forEach { (key, value) ->
map[key] = deepClone(value)
}
map as T
}
is List<*> -> {
val list = mutableListOf<Any?>()
obj.forEach { item ->
list.add(deepClone(item))
}
list as T
}
is MutableMap<*, *> -> deepClone(obj.toMap())
is MutableList<*> -> deepClone(obj.toList())
else -> obj
}
} catch (e: Exception) {
obj
}
}
data class User(val name: String, val address: Address)
data class Address(val city: String, val zip: String)
val original = User("Alice", Address("NYC", "10001"))
val cloned = deepClone(original)
// Note: This will copy the entire object structurePerform immutable updates on nested structures by copying at each level.
- Method: Copy and update path
- Path: Use dot notation for nested access
- Libraries: Use for state management
- Use case: Functional programming
// Immutable update
fun <T> updateImmutable(obj: Map<String, Any>, path: String, value: Any): Map<String, Any> {
val parts = path.split(".")
if (parts.size == 1) {
return obj + (parts[0] to value)
}
val first = parts[0]
val rest = parts.drop(1).joinToString(".")
val nested = obj[first] as? Map<String, Any> ?: emptyMap()
return obj + (first to updateImmutable(nested, rest, value))
}
val state = mapOf(
"user" to mapOf(
"name" to "Alice",
"age" to 25
)
)
val newState = updateImmutable(state, "user.age", 26)
println(state["user"]["age"]) // 25
println(newState["user"]["age"]) // 26Pipe composes functions from left to right.
- Method:
pipe(fns...)(value) - Implementation: Reduce with function application
- Use case: Function composition
- Direction: Left to right
// Pipe function
fun <T> pipe(vararg fns: (T) -> T): (T) -> T {
return { value ->
var result = value
for (fn in fns) {
result = fn(result)
}
result
}
}
val double: (Int) -> Int = { it * 2 }
val addTen: (Int) -> Int = { it + 10 }
val square: (Int) -> Int = { it * it }
val process = pipe(double, addTen, square)
println(process(5)) // (5*2+10)^2 = 400Compose functions from right to left.
- Method:
compose(fns...)(value) - Implementation: ReduceRight with function application
- Use case: Function composition
- Direction: Right to left
// Compose function
fun <T> compose(vararg fns: (T) -> T): (T) -> T {
return { value ->
var result = value
for (fn in fns.reversed()) {
result = fn(result)
}
result
}
}
val process2 = compose(square, addTen, double)
println(process2(5)) // (5*2+10)^2 = 400Cache function results based on arguments.
- Method: Cache in mutable map
- Key: Use arguments as key
- Use case: Expensive computations
- Trade-off: Memory for speed
// Memoization
fun <T, R> memoize(fn: (T) -> R): (T) -> R {
val cache = mutableMapOf<T, R>()
return { arg ->
cache.getOrPut(arg) {
fn(arg)
}
}
}
val fibonacciMemo = memoize { n: Int ->
when {
n <= 1 -> n
else -> fibonacciMemo(n - 1) + fibonacciMemo(n - 2)
}
}
println(fibonacciMemo(10))Ensure a function is called only once.
- Method: Use a flag and closure
- Implementation: Track if called
- Use case: Initialization
- Thread safety: Not needed in single-threaded
// Once function
fun <T> once(fn: () -> T): () -> T {
var called = false
var result: T? = null
return {
if (!called) {
called = true
result = fn()
}
result as T
}
}
val initialize = once {
println("Initialized")
mapOf("id" to 1, "name" to "App")
}
println(initialize()) // Prints "Initialized"
println(initialize()) // Returns cached resultDebounce with leading edge executes immediately then waits.
- Method: Track last call time
- Implementation: Immediate execution, cooldown
- Use case: Save actions, API calls
- Difference: Leading vs trailing edge
// Debounce with leading edge
fun debounceLeading(delayMs: Long, fn: () -> Unit): () -> Unit {
var lastCall = 0L
var timer: Thread? = null
return {
val now = System.currentTimeMillis()
if (now - lastCall < delayMs) {
timer?.interrupt()
timer = Thread {
Thread.sleep(delayMs)
lastCall = System.currentTimeMillis()
fn()
}.also { it.start() }
} else {
lastCall = now
fn()
}
}
}Throttle with leading edge executes at most once per time period.
- Method: Track last call time
- Implementation: Execute if enough time passed
- Use case: Scroll events, resize
- Difference: Leading vs trailing edge
// Throttle with leading edge
fun throttleLeading(delayMs: Long, fn: () -> Unit): () -> Unit {
var lastCall = 0L
return {
val now = System.currentTimeMillis()
if (now - lastCall >= delayMs) {
lastCall = now
fn()
}
}
}Deep equality comparison for nested structures.
- Method: Recursive comparison
- Base cases: Primitive values
- Maps: Compare entries recursively
- Lists: Compare elements recursively
// Deep equal
fun deepEqual(obj1: Any?, obj2: Any?): Boolean {
if (obj1 === obj2) return true
if (obj1 == null || obj2 == null) return false
if (obj1::class != obj2::class) return false
return when {
obj1 is Map<*, *> && obj2 is Map<*, *> -> {
if (obj1.size != obj2.size) return false
obj1.all { (key, value) ->
obj2[key]?.let { deepEqual(value, it) } ?: false
}
}
obj1 is List<*> && obj2 is List<*> -> {
if (obj1.size != obj2.size) return false
obj1.indices.all { deepEqual(obj1[it], obj2[it]) }
}
obj1 is Array<*> && obj2 is Array<*> -> {
if (obj1.size != obj2.size) return false
obj1.indices.all { deepEqual(obj1[it], obj2[it]) }
}
else -> obj1 == obj2
}
}Observable pattern for event notification.
- Observable: Maintains subscribers
- Subscribe: Add callback
- Notify: Call all subscribers
- Unsubscribe: Remove callback
// Observable pattern
class Observable<T> {
private val subscribers = mutableListOf<(T) -> Unit>()
fun subscribe(callback: (T) -> Unit): () -> Unit {
subscribers.add(callback)
return { subscribers.remove(callback) }
}
fun notify(data: T) {
subscribers.forEach { it(data) }
}
}
// Usage
val observable = Observable<String>()
val unsubscribe = observable.subscribe { data ->
println("Received: $data")
}
observable.notify("Hello") // Received: Hello
unsubscribe()
observable.notify("World") // Nothing happensSingleton pattern using object declaration.
- Method:
object Singleton - Thread-safe: Automatic initialization
- Global access:
Singleton.function() - Use case: Configuration, logging
// Singleton pattern
object Singleton {
private val data = mutableMapOf<String, Any>()
fun set(key: String, value: Any) {
data[key] = value
}
fun get(key: String): Any? {
return data[key]
}
}
// Usage
Singleton.set("name", "Alice")
println(Singleton.get("name")) // AliceFactory pattern for creating objects.
- Method: Factory class with create method
- Benefits: Decouples creation
- Sealed classes: Used with factory
- Use case: Creating different types
// Factory pattern
sealed class User {
data class Admin(val name: String) : User()
data class Guest(val name: String) : User()
data class RegularUser(val name: String) : User()
}
object UserFactory {
fun createUser(type: String, name: String): User {
return when (type) {
"admin" -> User.Admin(name)
"guest" -> User.Guest(name)
else -> User.RegularUser(name)
}
}
}
// Usage
val admin = UserFactory.createUser("admin", "Alice")
println(admin)Strategy pattern for interchangeable algorithms.
- Interface: Strategy interface
- Context: Uses strategy
- Benefits: Runtime switching
- Use case: Payment methods
// Strategy pattern
interface PaymentStrategy {
fun pay(amount: Double)
}
class CreditCardStrategy : PaymentStrategy {
override fun pay(amount: Double) {
println("Paid $$amount with Credit Card")
}
}
class PayPalStrategy : PaymentStrategy {
override fun pay(amount: Double) {
println("Paid $$amount with PayPal")
}
}
class CryptoStrategy : PaymentStrategy {
override fun pay(amount: Double) {
println("Paid $$amount with Crypto")
}
}
class PaymentContext(var strategy: PaymentStrategy) {
fun executePayment(amount: Double) {
strategy.pay(amount)
}
}
// Usage
val context = PaymentContext(CreditCardStrategy())
context.executePayment(100.0)
context.strategy = PayPalStrategy()
context.executePayment(50.0)Observer pattern for one-to-many notification.
- Subject: Maintains observers
- Observer: Receives updates
- Benefits: Loose coupling
- Use case: Event handling
// Observer pattern
interface Observer {
fun update(data: String)
}
class Subject {
private val observers = mutableListOf<Observer>()
var state: String = ""
set(value) {
field = value
notifyObservers()
}
fun attach(observer: Observer) {
observers.add(observer)
}
fun detach(observer: Observer) {
observers.remove(observer)
}
private fun notifyObservers() {
observers.forEach { it.update(state) }
}
}
class ConcreteObserver(private val name: String) : Observer {
override fun update(data: String) {
println("$name received: $data")
}
}
// Usage
val subject = Subject()
val observer1 = ConcreteObserver("Observer1")
val observer2 = ConcreteObserver("Observer2")
subject.attach(observer1)
subject.attach(observer2)
subject.state = "Hello World"Decorator pattern for adding behavior.
- Component: Base interface
- Decorator: Wraps component
- Benefits: Flexible extension
- Use case: Logging, authentication
// Decorator pattern
data class Coffee(
val cost: Double,
val description: String
)
fun milkDecorator(coffee: Coffee): Coffee {
return coffee.copy(
cost = coffee.cost + 2.0,
description = "${coffee.description}, Milk"
)
}
fun sugarDecorator(coffee: Coffee): Coffee {
return coffee.copy(
cost = coffee.cost + 1.0,
description = "${coffee.description}, Sugar"
)
}
// Usage
var coffee = Coffee(5.0, "Coffee")
coffee = milkDecorator(coffee)
coffee = sugarDecorator(coffee)
println(coffee.description) // Coffee, Milk, Sugar
println(coffee.cost) // 8.0Command pattern for encapsulating requests.
- Command: Encapsulates request
- Invoker: Executes commands
- Receiver: Performs work
- Benefits: Undo/redo, queuing
// Command pattern
interface Command {
fun execute()
fun undo()
}
class AddCommand(private val receiver: MutableList<Int>, private val value: Int) : Command {
override fun execute() {
receiver.add(value)
}
override fun undo() {
receiver.remove(value)
}
}
// Usage
val receiver = mutableListOf(1, 2, 3)
val cmd = AddCommand(receiver, 4)
cmd.execute()
println(receiver) // [1, 2, 3, 4]
cmd.undo()
println(receiver) // [1, 2, 3]Memento pattern for state capture and restoration.
- Originator: Creates/restores mementos
- Memento: Stores internal state
- Caretaker: Manages mementos
- Benefits: Undo/redo
// Memento pattern
data class Memento(val state: String)
class Originator {
var state: String = ""
set(value) {
field = value
println("State set to: $value")
}
fun saveState(): Memento = Memento(state)
fun restoreState(memento: Memento) {
state = memento.state
}
}
class Caretaker {
private val mementos = mutableListOf<Memento>()
fun addMemento(memento: Memento) {
mementos.add(memento)
}
fun getMemento(index: Int): Memento = mementos[index]
}
// Usage
val originator = Originator()
val caretaker = Caretaker()
originator.state = "State 1"
caretaker.addMemento(originator.saveState())
originator.state = "State 2"
caretaker.addMemento(originator.saveState())
originator.state = "State 3"
originator.restoreState(caretaker.getMemento(0))
println(originator.state) // State 1Mediator pattern for centralized communication.
- Mediator: Encapsulates communication
- Colleague: Communicates through mediator
- Benefits: Loose coupling
- Use case: Chat systems
// Mediator pattern
class Mediator {
private val colleagues = mutableListOf<Colleague>()
fun register(colleague: Colleague) {
colleagues.add(colleague)
}
fun send(message: String, sender: Colleague) {
colleagues.filter { it != sender }.forEach {
it.receive(message)
}
}
}
class Colleague(
val name: String,
private val mediator: Mediator
) {
init {
mediator.register(this)
}
fun send(message: String) {
mediator.send(message, this)
}
fun receive(message: String) {
println("$name received: $message")
}
}
// Usage
val mediator = Mediator()
val alice = Colleague("Alice", mediator)
val bob = Colleague("Bob", mediator)
alice.send("Hello Bob!")Chain of Responsibility for processing requests.
- Handler: Processes or forwards
- Chain: Linked list of handlers
- Benefits: Decoupling
- Use case: Logging, authentication
// Chain of Responsibility
abstract class Handler {
var nextHandler: Handler? = null
fun setNext(handler: Handler): Handler {
nextHandler = handler
return handler
}
abstract fun handle(request: Map<String, Any>)
}
class AuthHandler : Handler() {
override fun handle(request: Map<String, Any>) {
if (request.containsKey("token")) {
println("Authentication passed")
nextHandler?.handle(request)
} else {
println("Authentication failed")
}
}
}
class LoggerHandler : Handler() {
override fun handle(request: Map<String, Any>) {
println("Logging request: ${request["url"]}")
nextHandler?.handle(request)
}
}
// Usage
val auth = AuthHandler()
val logger = LoggerHandler()
auth.setNext(logger)
auth.handle(mapOf("token" to "valid", "url" to "/api"))State pattern for changing behavior with state.
- Context: Maintains state
- State: Defines behavior
- Benefits: Clean state management
- Use case: State machines
// State pattern
interface State {
fun handle()
}
class ReadyState : State {
override fun handle() {
println("Ready: Waiting for input")
}
}
class ProcessingState : State {
override fun handle() {
println("Processing: Working on task")
}
}
class CompletedState : State {
override fun handle() {
println("Completed: Task finished")
}
}
class Context {
var state: State = ReadyState()
set(value) {
field = value
}
fun request() {
state.handle()
}
}
// Usage
val context = Context()
context.request() // Ready: Waiting for input
context.state = ProcessingState()
context.request() // Processing: Working on task
context.state = CompletedState()
context.request() // Completed: Task finishedProxy pattern for controlling access.
- Subject: Real object
- Proxy: Controls access
- Benefits: Access control
- Use case: Virtual proxies
// Proxy pattern
class RealSubject {
fun request() {
println("RealSubject: Handling request")
}
}
class Proxy {
private var realSubject: RealSubject? = null
fun request() {
if (checkAccess()) {
realSubject = realSubject ?: RealSubject()
realSubject?.request()
logAccess()
}
}
private fun checkAccess(): Boolean {
println("Proxy: Checking access")
return true
}
private fun logAccess() {
println("Proxy: Logging access")
}
}
// Usage
val proxy = Proxy()
proxy.request()Flyweight pattern for sharing objects.
- Flyweight: Shared object
- Factory: Manages flyweights
- Benefits: Memory optimization
- Use case: Text rendering
// Flyweight pattern
class Flyweight(private val sharedState: String) {
fun operation(uniqueState: String) {
println("Shared: $sharedState, Unique: $uniqueState")
}
}
class FlyweightFactory {
private val flyweights = mutableMapOf<String, Flyweight>()
fun getFlyweight(sharedState: String): Flyweight {
return flyweights.getOrPut(sharedState) {
Flyweight(sharedState)
}
}
}
// Usage
val factory = FlyweightFactory()
val fw1 = factory.getFlyweight("state1")
val fw2 = factory.getFlyweight("state1")
val fw3 = factory.getFlyweight("state2")
fw1.operation("unique1")
fw2.operation("unique2")
fw3.operation("unique3")Bridge pattern for separating abstraction from implementation.
- Abstraction: High-level interface
- Implementation: Low-level operations
- Benefits: Separation of concerns
- Use case: Cross-platform
// Bridge pattern
interface Implementation {
fun operation()
}
class ConcreteImplementationA : Implementation {
override fun operation() {
println("ConcreteImplementationA: Operation")
}
}
class ConcreteImplementationB : Implementation {
override fun operation() {
println("ConcreteImplementationB: Operation")
}
}
class Abstraction(private var impl: Implementation) {
fun operation() {
println("Abstraction: Additional logic")
impl.operation()
}
}
// Usage
val implA = ConcreteImplementationA()
val implB = ConcreteImplementationB()
val abstraction1 = Abstraction(implA)
val abstraction2 = Abstraction(implB)
abstraction1.operation()
abstraction2.operation()Adapter pattern for converting interfaces.
- Target: Expected interface
- Adaptee: Existing interface
- Adapter: Bridges interfaces
- Benefits: Reusability
// Adapter pattern
class Target {
fun request() {
println("Target: Request")
}
}
class Adaptee {
fun specificRequest() {
println("Adaptee: Specific Request")
}
}
class Adapter(private val adaptee: Adaptee) {
fun request() {
adaptee.specificRequest()
}
}
// Usage
val adaptee = Adaptee()
val adapter = Adapter(adaptee)
adapter.request()Facade pattern for simplifying subsystems.
- Facade: Simplified interface
- Subsystem: Complex components
- Benefits: Simplified interface
- Use case: Library APIs
// Facade pattern
class SubsystemA {
fun operationA() {
println("SubsystemA: Operation")
}
}
class SubsystemB {
fun operationB() {
println("SubsystemB: Operation")
}
}
class Facade {
private val subsystemA = SubsystemA()
private val subsystemB = SubsystemB()
fun operation() {
subsystemA.operationA()
subsystemB.operationB()
println("Facade: Complex operation")
}
}
// Usage
val facade = Facade()
facade.operation()Composite pattern for tree structures.
- Component: Interface for all
- Leaf: Individual object
- Composite: Container
- Benefits: Uniform interface
// Composite pattern
interface Component {
fun operation()
}
class Leaf(private val name: String) : Component {
override fun operation() {
println("Leaf $name: Operation")
}
}
class Composite(private val name: String) : Component {
private val children = mutableListOf<Component>()
fun add(component: Component) {
children.add(component)
}
fun remove(component: Component) {
children.remove(component)
}
override fun operation() {
println("Composite $name: Operation")
children.forEach { it.operation() }
}
}
// Usage
val leaf1 = Leaf("A")
val leaf2 = Leaf("B")
val composite = Composite("Root")
composite.add(leaf1)
composite.add(leaf2)
composite.operation()Visitor pattern for adding operations.
- Visitor: Defines operations
- Element: Accepts visitors
- Benefits: Adding operations without modifying
- Use case: Compilers, AST
// Visitor pattern
interface Element {
fun accept(visitor: Visitor)
}
class ElementA : Element {
override fun accept(visitor: Visitor) {
visitor.visit(this)
}
}
class ElementB : Element {
override fun accept(visitor: Visitor) {
visitor.visit(this)
}
}
interface Visitor {
fun visit(element: ElementA)
fun visit(element: ElementB)
}
class ConcreteVisitor : Visitor {
override fun visit(element: ElementA) {
println("Visiting ElementA")
}
override fun visit(element: ElementB) {
println("Visiting ElementB")
}
}
// Usage
val visitor = ConcreteVisitor()
val elementA = ElementA()
val elementB = ElementB()
elementA.accept(visitor)
elementB.accept(visitor)Iterator pattern for sequential access.
- Iterator: Traverses collection
- Aggregate: Creates iterator
- Benefits: Uniform traversal
- Use case: Collection traversal
// Iterator pattern
class Iterator<T>(private val collection: List<T>) {
private var index = 0
fun next(): T? {
return if (hasNext()) collection[index++] else null
}
fun hasNext(): Boolean {
return index < collection.size
}
}
class CustomCollection<T> {
private val items = mutableListOf<T>()
fun add(item: T) {
items.add(item)
}
fun getIterator(): Iterator<T> {
return Iterator(items)
}
}
// Usage
val collection = CustomCollection<String>()
collection.add("A")
collection.add("B")
collection.add("C")
val iterator = collection.getIterator()
while (iterator.hasNext()) {
println(iterator.next())
}Template Method for algorithm skeletons.
- AbstractClass: Defines template
- ConcreteClass: Implements steps
- Benefits: Code reuse
- Use case: Frameworks
// Template Method pattern
abstract class AbstractClass {
fun templateMethod() {
step1()
step2()
step3()
}
open fun step1() {
println("Step 1")
}
abstract fun step2()
open fun step3() {
println("Step 3")
}
}
class ConcreteClass : AbstractClass() {
override fun step2() {
println("Concrete Step 2")
}
}
// Usage
val concrete = ConcreteClass()
concrete.templateMethod()Builder pattern for constructing complex objects.
- Builder: Constructs parts
- Director: Orchestrates construction
- Product: Constructed object
- Benefits: Step-by-step construction
// Builder pattern
class Product {
private val parts = mutableListOf<String>()
fun add(part: String) {
parts.add(part)
}
fun listParts() {
println(parts.joinToString(", "))
}
}
class Builder {
private val product = Product()
fun reset() {
product.parts.clear()
}
fun buildStepA() {
product.add("Part A")
}
fun buildStepB() {
product.add("Part B")
}
fun getResult(): Product {
return product
}
}
class Director(private val builder: Builder) {
fun buildMinimal() {
builder.buildStepA()
}
fun buildFull() {
builder.buildStepA()
builder.buildStepB()
}
}
// Usage
val builder = Builder()
val director = Director(builder)
director.buildMinimal()
val product = builder.getResult()
product.listParts()Prototype pattern for cloning objects.
- Prototype: Cloneable object
- Clone: Creates a copy
- Benefits: Performance
- Use case: Complex objects
// Prototype pattern
data class Prototype(
val name: String,
val nested: MutableMap<String, Any>
) {
fun clone(): Prototype {
return copy(
nested = nested.toMutableMap()
)
}
fun deepClone(): Prototype {
return copy(
nested = nested.mapValues { (_, value) ->
when (value) {
is MutableMap<*, *> -> (value as MutableMap<String, Any>).toMutableMap()
is List<*> -> value.toMutableList()
else -> value
}
}.toMutableMap()
)
}
}
// Usage
val original = Prototype("Original", mutableMapOf("value" to 42))
val copy = original.clone()
copy.name = "Copy"
copy.nested["value"] = 99
println(original.name) // Original
println(original.nested["value"]) // 42 (shallow copy)
val deepCopy = original.deepClone()
deepCopy.nested["value"] = 100
println(original.nested["value"]) // 42 (deep copy)