Back to Subjects
Kotlin Cheatsheet
Comprehensive Kotlin programming guide with syntax, Android development, coroutines, and best practices.
Kotlin Programming Cheatsheet
Basic Syntax
Hello World
fun main() {
println("Hello, World!")
}
// Alternative with args
fun main(args: Array<String>) {
println("Hello, Kotlin!")
}
Variables & Constants
// Variables (mutable)
var name = "Kotlin"
var age = 10
var isAwesome = true
// Constants (immutable)
val pi = 3.14159
val appName = "My App"
// Type annotations
var message: String = "Hello"
var count: Int = 42
var price: Double = 9.99
var isReady: Boolean = false
// Nullable variables
var optionalName: String? = null
optionalName = "John"
// Late initialization
lateinit var userData: String
// Lazy initialization
val expensiveValue: String by lazy {
// Computed only when first accessed
"Expensive computation result"
}
Data Types
Basic Types
// Numbers
val byte: Byte = 127
val short: Short = 32767
val int: Int = 42
val long: Long = 123456789L
val float: Float = 3.14f
val double: Double = 3.14159
val unsignedInt: UInt = 42u
// Characters and Strings
val char: Char = 'A'
val string: String = "Hello"
val multilineString = """
This is a
multiline string
""".trimIndent()
// String interpolation
val greeting = "Hello, $name!"
val expression = "2 + 2 = ${2 + 2}"
// Booleans
val isTrue: Boolean = true
val isFalse: Boolean = false
// Type checking and casting
if (obj is String) {
// Smart cast - obj is automatically cast to String
println(obj.uppercase())
}
val str = obj as String // Unsafe cast
val str2 = obj as? String // Safe cast (returns null if fails)
Collections
// Lists
val readOnlyList = listOf("apple", "banana", "orange")
val mutableList = mutableListOf("apple", "banana", "orange")
val emptyList = emptyList<String>()
// List operations
mutableList.add("grape")
mutableList.addAll(listOf("mango", "kiwi"))
mutableList.removeAt(0)
val first = mutableList.first()
val last = mutableList.last()
val size = mutableList.size
// Sets
val readOnlySet = setOf(1, 2, 3, 2, 1) // {1, 2, 3}
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
mutableSet.remove(2)
// Maps
val readOnlyMap = mapOf("name" to "John", "age" to 30)
val mutableMap = mutableMapOf("name" to "John", "age" to 30)
mutableMap["city"] = "New York"
mutableMap.remove("age")
// Accessing map values
val name = mutableMap["name"] // Returns String?
val nameOrDefault = mutableMap.getOrDefault("name", "Unknown")
// Arrays
val array = arrayOf(1, 2, 3, 4, 5)
val typedArray: Array<String> = arrayOf("a", "b", "c")
val primitiveArray = intArrayOf(1, 2, 3, 4, 5)
Functions
Basic Functions
// Simple function
fun greet() {
println("Hello!")
}
// Function with parameters
fun greet(name: String) {
println("Hello, $name!")
}
// Function with return type
fun add(a: Int, b: Int): Int {
return a + b
}
// Single expression function
fun multiply(a: Int, b: Int): Int = a * b
// Function with default parameters
fun greet(name: String = "World", enthusiastic: Boolean = false) {
val greeting = if (enthusiastic) "Hello, $name!" else "Hello, $name."
println(greeting)
}
// Named arguments
greet(enthusiastic = true, name = "Kotlin")
// Vararg parameters
fun sum(vararg numbers: Int): Int {
var total = 0
for (number in numbers) {
total += number
}
return total
}
val result = sum(1, 2, 3, 4, 5)
Higher-Order Functions
// Function as parameter
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
val result = calculate(5, 3) { x, y -> x + y }
// Function returning function
fun getOperation(type: String): (Int, Int) -> Int {
return when (type) {
"add" -> { a, b -> a + b }
"multiply" -> { a, b -> a * b }
else -> { a, b -> 0 }
}
}
// Lambda expressions
val square: (Int) -> Int = { x -> x * x }
val isEven = { number: Int -> number % 2 == 0 }
// it parameter (single parameter lambda)
val doubled = listOf(1, 2, 3).map { it * 2 }
val filtered = listOf(1, 2, 3, 4, 5).filter { it > 3 }
// Function literals with receiver
fun buildString(builderAction: StringBuilder.() -> Unit): String {
val sb = StringBuilder()
sb.builderAction()
return sb.toString()
}
val result = buildString {
append("Hello")
append(" ")
append("World")
}
Control Flow
Conditionals
// If expression
val max = if (a > b) a else b
val result = if (condition) {
"True case"
} else {
"False case"
}
// When expression (like switch)
val grade = when (score) {
in 90..100 -> "A"
in 80..89 -> "B"
in 70..79 -> "C"
in 60..69 -> "D"
else -> "F"
}
// When with conditions
val description = when {
age < 13 -> "Child"
age < 20 -> "Teenager"
age < 65 -> "Adult"
else -> "Senior"
}
// When with type checking
val result = when (x) {
is String -> x.length
is Int -> x * 2
is List<*> -> x.size
else -> -1
}
Loops
// For loops
for (i in 1..5) {
println(i) // 1, 2, 3, 4, 5
}
for (i in 1 until 5) {
println(i) // 1, 2, 3, 4
}
for (i in 5 downTo 1) {
println(i) // 5, 4, 3, 2, 1
}
for (i in 1..10 step 2) {
println(i) // 1, 3, 5, 7, 9
}
// Iterating over collections
val fruits = listOf("apple", "banana", "orange")
for (fruit in fruits) {
println(fruit)
}
for ((index, fruit) in fruits.withIndex()) {
println("$index: $fruit")
}
// While loops
var counter = 0
while (counter < 3) {
println(counter)
counter++
}
// Do-while loops
do {
println("This runs at least once")
counter--
} while (counter > 0)
// Break and continue
for (i in 1..10) {
if (i == 5) continue
if (i == 8) break
println(i)
}
// Labeled breaks
outer@ for (i in 1..3) {
for (j in 1..3) {
if (i == 2 && j == 2) break@outer
println("$i, $j")
}
}
Null Safety
Nullable Types
var name: String? = null
// Safe call operator
val length = name?.length
// Elvis operator
val len = name?.length ?: 0
// Safe cast
val str: String? = obj as? String
// Let function
name?.let {
println("Name is $it")
// This block runs only if name is not null
}
// Also function
val person = Person().also {
it.name = "John"
it.age = 30
}
// Run function
val result = name?.run {
uppercase().substring(0, 3)
}
// Not-null assertion (use carefully!)
val length = name!!.length // Throws exception if name is null
Safe Calls and Elvis Operator
// Chaining safe calls
val city = person?.address?.city
// Elvis with function calls
val name = getName() ?: getDefaultName()
// Elvis with return/throw
fun processUser(user: User?) {
val name = user?.name ?: return
val email = user.email ?: throw IllegalArgumentException("Email required")
// Process user...
}
// Safe collections operations
val list: List<String>? = getList()
val firstItem = list?.firstOrNull()
val size = list?.size ?: 0
Classes and Objects
Basic Classes
// Simple class
class Person {
var name: String = ""
var age: Int = 0
fun introduce() {
println("Hi, I'm $name and I'm $age years old")
}
}
// Primary constructor
class Person(val name: String, var age: Int) {
init {
println("Person created: $name")
}
fun introduce() = println("Hi, I'm $name and I'm $age years old")
}
// Secondary constructors
class Person(val name: String) {
var age: Int = 0
constructor(name: String, age: Int) : this(name) {
this.age = age
}
}
// Properties with custom getters/setters
class Rectangle(val width: Double, val height: Double) {
val area: Double
get() = width * height
var isSquare: Boolean
get() = width == height
set(value) {
if (value) {
// Make it square by setting height = width
// Note: This is just an example, properties are immutable here
}
}
}
Inheritance
// Open class (can be inherited)
open class Animal(val name: String) {
open fun makeSound() {
println("The animal makes a sound")
}
open val species: String = "Unknown"
}
class Dog(name: String) : Animal(name) {
override fun makeSound() {
println("$name barks")
}
override val species: String = "Canine"
fun fetch() {
println("$name fetches the ball")
}
}
// Abstract classes
abstract class Shape {
abstract val area: Double
abstract fun draw()
fun describe() {
println("This is a shape with area $area")
}
}
class Circle(val radius: Double) : Shape() {
override val area: Double
get() = Math.PI * radius * radius
override fun draw() {
println("Drawing a circle")
}
}
Data Classes
// Data class
data class User(val name: String, val email: String, val age: Int)
val user1 = User("John", "john@example.com", 30)
val user2 = User("John", "john@example.com", 30)
// Automatically generated methods
println(user1 == user2) // true (equals)
println(user1) // User(name=John, email=john@example.com, age=30) (toString)
// Copy with modifications
val user3 = user1.copy(age = 31)
// Destructuring
val (name, email, age) = user1
println("Name: $name, Email: $email, Age: $age")
Object Declarations and Expressions
// Singleton object
object DatabaseManager {
fun connect() {
println("Connecting to database...")
}
fun disconnect() {
println("Disconnecting from database...")
}
}
// Usage
DatabaseManager.connect()
// Object expression (anonymous object)
val clickListener = object : OnClickListener {
override fun onClick() {
println("Button clicked!")
}
}
// Companion object
class MyClass {
companion object {
const val CONSTANT = "Hello"
fun create(): MyClass {
return MyClass()
}
}
}
// Usage
val instance = MyClass.create()
println(MyClass.CONSTANT)
Interfaces
Basic Interfaces
interface Drawable {
fun draw()
// Interface with default implementation
fun describe(): String = "This is a drawable object"
}
interface Resizable {
fun resize(factor: Double)
}
class Rectangle : Drawable, Resizable {
override fun draw() {
println("Drawing a rectangle")
}
override fun resize(factor: Double) {
println("Resizing rectangle by factor $factor")
}
}
// Interface with properties
interface Named {
val name: String
val displayName: String
get() = name.uppercase()
}
class Person(override val name: String) : Named
// Functional interfaces (SAM - Single Abstract Method)
fun interface StringProcessor {
fun process(input: String): String
}
val processor = StringProcessor { it.uppercase() }
Generics
Generic Classes and Functions
// Generic class
class Box<T>(var value: T) {
fun get(): T = value
fun set(newValue: T) {
value = newValue
}
}
val stringBox = Box("Hello")
val intBox = Box(42)
// Generic functions
fun <T> identity(value: T): T = value
fun <T> List<T>.secondOrNull(): T? =
if (size >= 2) this[1] else null
// Multiple type parameters
fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> {
val map = mutableMapOf<K, V>()
for (pair in pairs) {
map[pair.first] = pair.second
}
return map
}
// Generic constraints
fun <T : Number> sum(list: List<T>): Double {
return list.sumOf { it.toDouble() }
}
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<T>
where T : CharSequence, T : Comparable<T> {
return list.filter { it > threshold }
}
Variance
// Covariance (out)
interface Producer<out T> {
fun produce(): T
}
class StringProducer : Producer<String> {
override fun produce(): String = "Hello"
}
val stringProducer: Producer<String> = StringProducer()
val anyProducer: Producer<Any> = stringProducer // OK
// Contravariance (in)
interface Consumer<in T> {
fun consume(item: T)
}
class AnyConsumer : Consumer<Any> {
override fun consume(item: Any) {
println("Consuming: $item")
}
}
val anyConsumer: Consumer<Any> = AnyConsumer()
val stringConsumer: Consumer<String> = anyConsumer // OK
// Star projection
fun printList(list: List<*>) {
for (item in list) {
println(item)
}
}
Coroutines
Basic Coroutines
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Hello")
delay(1000L)
println("World!")
}
// Launching coroutines
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
// Async and await
suspend fun fetchUser(id: Int): User {
delay(1000) // Simulate network call
return User("User$id", "user$id@example.com", 25)
}
fun main() = runBlocking {
val deferred1 = async { fetchUser(1) }
val deferred2 = async { fetchUser(2) }
val user1 = deferred1.await()
val user2 = deferred2.await()
println("Users: $user1, $user2")
}
Coroutine Builders and Scopes
// Different coroutine builders
fun main() = runBlocking {
// launch - fire and forget
launch {
println("Launch")
}
// async - returns a result
val deferred = async {
"Result"
}
println(deferred.await())
// withContext - switches context
withContext(Dispatchers.IO) {
// IO operations
}
}
// Coroutine scope
class MyRepository {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun fetchData() {
scope.launch {
// Network operation
}
}
fun cleanup() {
scope.cancel()
}
}
Extensions
Extension Functions
// Extension function for String
fun String.isEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
// Usage
val email = "user@example.com"
println(email.isEmail()) // true
// Extension function with receiver
fun StringBuilder.appendLine(text: String): StringBuilder {
return this.append(text).append("\n")
}
// Extension functions for collections
fun <T> List<T>.second(): T {
if (size < 2) throw NoSuchElementException("List has fewer than 2 elements")
return this[1]
}
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val temp = this[index1]
this[index1] = this[index2]
this[index2] = temp
}
// Extension properties
val String.lastChar: Char
get() = this[length - 1]
var StringBuilder.lastChar: Char
get() = this[length - 1]
set(value) {
this.setCharAt(length - 1, value)
}
Extension Functions on Nullable Types
fun String?.isNullOrEmpty(): Boolean {
return this == null || this.isEmpty()
}
fun String?.orDefault(default: String): String {
return if (this.isNullOrEmpty()) default else this
}
// Usage
val name: String? = null
println(name.isNullOrEmpty()) // true
println(name.orDefault("Unknown")) // "Unknown"
Scope Functions
let, run, with, apply, also
// let - execute a code block and return the result
val result = "Hello".let {
println(it)
it.length
}
val name: String? = getName()
name?.let {
println("Name is $it")
processName(it)
}
// run - execute a code block and return the result
val result = "Hello".run {
println(this)
this.length
}
// with - execute a code block with the object as receiver
val result = with(StringBuilder()) {
append("Hello")
append(" ")
append("World")
toString()
}
// apply - execute a code block and return the object
val person = Person().apply {
name = "John"
age = 30
city = "New York"
}
// also - execute a code block and return the object
val numbers = mutableListOf(1, 2, 3).also {
println("Before: $it")
}.also {
it.add(4)
}.also {
println("After: $it")
}
Delegation
Property Delegation
// Built-in delegates
class User {
val name: String by lazy {
println("Computing name...")
"John Doe"
}
var age: Int by Delegates.observable(0) { property, oldValue, newValue ->
println("$oldValue -> $newValue")
}
var city: String by Delegates.vetoable("") { property, oldValue, newValue ->
newValue.isNotEmpty() // Only allow non-empty values
}
}
// Custom delegate
class Preference<T>(private val key: String, private val defaultValue: T) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
// Get value from preferences
return defaultValue // Simplified
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
// Save value to preferences
println("Saving $key = $value")
}
}
class Settings {
var username: String by Preference("username", "")
var theme: String by Preference("theme", "light")
}
Class Delegation
interface Base {
fun doSomething()
fun doSomethingElse()
}
class BaseImpl : Base {
override fun doSomething() {
println("BaseImpl: doSomething")
}
override fun doSomethingElse() {
println("BaseImpl: doSomethingElse")
}
}
class Derived(base: Base) : Base by base {
override fun doSomething() {
println("Derived: doSomething")
// Can still override specific methods
}
}
val base = BaseImpl()
val derived = Derived(base)
derived.doSomething() // Derived: doSomething
derived.doSomethingElse() // BaseImpl: doSomethingElse
Useful Standard Library Functions
Collection Operations
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Filter and map
val evenSquares = numbers
.filter { it % 2 == 0 }
.map { it * it }
// Find operations
val firstEven = numbers.find { it % 2 == 0 }
val firstEvenIndex = numbers.indexOfFirst { it % 2 == 0 }
// Grouping
val grouped = numbers.groupBy { it % 3 }
// {1=[1, 4, 7, 10], 2=[2, 5, 8], 0=[3, 6, 9]}
// Partition
val (evens, odds) = numbers.partition { it % 2 == 0 }
// Reduce and fold
val sum = numbers.reduce { acc, n -> acc + n }
val product = numbers.fold(1) { acc, n -> acc * n }
// Take and drop
val first3 = numbers.take(3)
val without3 = numbers.drop(3)
val takeWhileSmall = numbers.takeWhile { it < 5 }
// Zip
val letters = listOf("a", "b", "c")
val zipped = numbers.zip(letters) // [(1, a), (2, b), (3, c)]
// Flatten
val listOfLists = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6))
val flattened = listOfLists.flatten() // [1, 2, 3, 4, 5, 6]
val flatMapped = listOf("hello", "world").flatMap { it.toList() }
// [h, e, l, l, o, w, o, r, l, d]
String Operations
val text = "Hello, World!"
// Basic operations
val upper = text.uppercase()
val lower = text.lowercase()
val trimmed = " hello ".trim()
// Checking content
val startsWithHello = text.startsWith("Hello")
val endsWithExclamation = text.endsWith("!")
val containsWorld = text.contains("World")
// Splitting and joining
val words = text.split(", ")
val joined = listOf("Hello", "World").joinToString(" ")
// Regex
val phonePattern = Regex("""\d{3}-\d{3}-\d{4}""")
val isValidPhone = phonePattern.matches("123-456-7890")
val emailPattern = Regex("""[\w\.-]+@[\w\.-]+\.\w+""")
val emails = text.findAll(emailPattern).map { it.value }.toList()
// String building
val built = buildString {
append("Hello")
appendLine()
append("World")
}
Android Development with Kotlin
Activity Example
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener {
binding.textView.text = "Button clicked!"
}
}
}
ViewModel Example
class UserViewModel : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
fun loadUsers() {
viewModelScope.launch {
_loading.value = true
try {
val userList = userRepository.getUsers()
_users.value = userList
} catch (e: Exception) {
// Handle error
} finally {
_loading.value = false
}
}
}
}
Best Practices
- Prefer
valovervar- Use immutable variables when possible - Use data classes for simple data holders
- Leverage null safety - Use safe calls and elvis operator
- Use scope functions appropriately -
let,run,apply,also,with - Prefer extension functions over utility classes
- Use coroutines for asynchronous programming
- Follow naming conventions - camelCase for functions/variables, PascalCase for classes
- Use type inference when the type is obvious
- Prefer collections operations over manual loops
- Use sealed classes for representing restricted hierarchies