Overview
Kotlin is a modern, statically-typed programming language that runs on the JVM, Android, JavaScript, and Native platforms. Developed by JetBrains and officially supported by Google for Android development since 2017, Kotlin offers concise syntax, null safety, and seamless Java interoperability. It has become the preferred language for Android development, with over 95% of top Android apps using Kotlin.
1. Kotlin Basics
Variables and Data Types
// Immutable (read-only)
val name: String = "John"
val age = 25 // Type inference
// Mutable
var count: Int = 0
var price = 19.99 // Double
// Basic types
val byte: Byte = 127
val short: Short = 32767
val int: Int = 2147483647
val long: Long = 9223372036854775807L
val float: Float = 3.14f
val double: Double = 3.14159
val char: Char = 'A'
val boolean: Boolean = true
Null Safety
// Nullable types
var nullable: String? = null
val length = nullable?.length // Safe call
val lengthOrZero = nullable?.length ?: 0 // Elvis operator
// Non-null assertion (use carefully!)
val length2 = nullable!!.length // Throws NPE if null
// Safe cast
val str: String? = obj as? String
String Templates
val name = "Alice"
val greeting = "Hello, $name"
val expression = "2 + 2 = ${2 + 2}"
// Multiline strings
val text = """
|First line
|Second line
""".trimMargin()
2. Functions
Basic Functions
// Function with return type
fun add(a: Int, b: Int): Int {
return a + b
}
// Single expression function
fun multiply(a: Int, b: Int) = a * b
// Default parameters
fun greet(name: String = "Guest") = "Hello, $name"
// Named arguments
greet(name = "John")
Extension Functions
// Extend existing classes
fun String.isPalindrome(): Boolean {
return this == this.reversed()
}
"radar".isPalindrome() // true
// Extension properties
val String.lastChar: Char
get() = this[length - 1]
Higher-Order Functions
// Function as parameter
fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
return operation(x, y)
}
calculate(5, 3, { a, b -> a + b }) // 8
calculate(5, 3) { a, b -> a * b } // 15 (trailing lambda)
// Function returning function
fun getOperation(op: String): (Int, Int) -> Int {
return when(op) {
"+" -> { a, b -> a + b }
"*" -> { a, b -> a * b }
else -> { _, _ -> 0 }
}
}
3. Classes and Objects
Classes
// Primary constructor
class Person(val name: String, var age: Int) {
// Secondary constructor
constructor(name: String) : this(name, 0)
// Init block
init {
println("Person created: $name")
}
}
// Data class (equals, hashCode, toString, copy)
data class User(val id: Int, val name: String)
// Sealed class (restricted hierarchy)
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
}
Inheritance
open class Animal(val name: String) {
open fun sound() = "Some sound"
}
class Dog(name: String) : Animal(name) {
override fun sound() = "Woof!"
}
// Interfaces
interface Clickable {
fun click()
fun showOff() = println("I'm clickable!") // Default implementation
}
class Button : Clickable {
override fun click() = println("Button clicked")
}
Object and Companion Object
// Singleton
object Database {
fun connect() { /* ... */ }
}
class MyClass {
companion object {
const val CONSTANT = "VALUE"
fun factory(): MyClass = MyClass()
}
}
4. Collections
List, Set, Map
// Immutable collections
val list = listOf(1, 2, 3)
val set = setOf("a", "b", "c")
val map = mapOf("key1" to "value1", "key2" to "value2")
// Mutable collections
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4)
val mutableMap = mutableMapOf<String, Int>()
mutableMap["key"] = 10
Collection Operations
val numbers = listOf(1, 2, 3, 4, 5)
// Transformations
numbers.map { it * 2 } // [2, 4, 6, 8, 10]
numbers.filter { it % 2 == 0 } // [2, 4]
numbers.flatMap { listOf(it, it * 2) } // [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]
// Aggregations
numbers.sum() // 15
numbers.reduce { acc, n -> acc + n } // 15
numbers.fold(0) { acc, n -> acc + n } // 15
// Grouping
val words = listOf("apple", "banana", "apricot", "berry")
words.groupBy { it.first() } // {a=[apple, apricot], b=[banana, berry]}
// Sequences (lazy evaluation)
numbers.asSequence()
.filter { it % 2 == 0 }
.map { it * 2 }
.toList()
5. Coroutines
Basics
// Launch coroutine
GlobalScope.launch {
delay(1000) // Non-blocking delay
println("World!")
}
println("Hello,")
// Async/await
val deferred = async {
delay(1000)
"Result"
}
val result = deferred.await()
// RunBlocking (for testing/main)
runBlocking {
delay(1000)
println("Done")
}
Coroutine Scope
class MyActivity : CoroutineScope {
private val job = Job()
override val coroutineContext = Dispatchers.Main + job
fun loadData() {
launch {
val data = withContext(Dispatchers.IO) {
// Network/DB operation
fetchData()
}
updateUI(data)
}
}
fun onDestroy() {
job.cancel() // Cancel all coroutines
}
}
Flow
// Create flow
fun numbersFlow(): Flow<Int> = flow {
for (i in 1..5) {
delay(100)
emit(i)
}
}
// Collect flow
numbersFlow()
.map { it * 2 }
.filter { it > 5 }
.collect { println(it) }
// StateFlow (hot flow)
val stateFlow = MutableStateFlow(0)
stateFlow.value = 1
6. Android Specific
Activity/Fragment
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// View binding
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener {
// Handle click
}
}
}
ViewModel
class MyViewModel : ViewModel() {
private val _uiState = MutableLiveData<String>()
val uiState: LiveData<String> = _uiState
fun updateData() {
viewModelScope.launch {
val result = repository.fetchData()
_uiState.value = result
}
}
}
// In Activity/Fragment
viewModel.uiState.observe(this) { data ->
// Update UI
}
Jetpack Compose Basics
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
@Composable
fun MyScreen() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
7. Delegation
Property Delegation
// Lazy initialization
val expensive: String by lazy {
println("Computing...")
"Result"
}
// Observable properties
var name: String by Delegates.observable("initial") { _, old, new ->
println("$old -> $new")
}
// Custom delegate
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Delegated value"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("Setting $value")
}
}
8. Generics
Generic Classes and Functions
// Generic class
class Box<T>(val content: T)
// Generic function
fun <T> singletonList(item: T): List<T> = listOf(item)
// Constraints
fun <T : Comparable<T>> max(a: T, b: T): T {
return if (a > b) a else b
}
// Multiple constraints
fun <T> process(item: T) where T : CharSequence, T : Comparable<T> {
// ...
}
Variance
// Covariance (out) - Producer
interface Producer<out T> {
fun produce(): T
}
// Contravariance (in) - Consumer
interface Consumer<in T> {
fun consume(item: T)
}
// Invariance (default)
class Box<T>(var value: T)
// Star projection
fun printAll(items: List<*>) {
items.forEach { println(it) }
}
9. Scope Functions
val person = Person("Alice", 25)
// let - it
person.let {
println("${it.name} is ${it.age} years old")
}
// run - this
val result = person.run {
age += 1
"Happy ${age}th birthday, $name!"
}
// with - this
with(person) {
println("$name is $age years old")
}
// apply - this (returns object)
val configured = person.apply {
age = 30
name = "Alice Smith"
}
// also - it (returns object)
person.also {
println("Processing ${it.name}")
}
10. Common Interview Patterns
Singleton Pattern
// Object declaration
object Singleton {
fun doSomething() { }
}
// Class with companion object
class Singleton private constructor() {
companion object {
@JvmStatic
val instance = Singleton()
}
}
Builder Pattern
class Pizza private constructor(
val size: String,
val cheese: Boolean,
val pepperoni: Boolean
) {
class Builder {
private var size = "medium"
private var cheese = true
private var pepperoni = false
fun size(size: String) = apply { this.size = size }
fun cheese(value: Boolean) = apply { cheese = value }
fun pepperoni(value: Boolean) = apply { pepperoni = value }
fun build() = Pizza(size, cheese, pepperoni)
}
}
val pizza = Pizza.Builder()
.size("large")
.pepperoni(true)
.build()
Factory Pattern
interface Animal {
fun speak()
}
class Dog : Animal {
override fun speak() = println("Woof!")
}
class Cat : Animal {
override fun speak() = println("Meow!")
}
object AnimalFactory {
fun createAnimal(type: String): Animal = when(type) {
"dog" -> Dog()
"cat" -> Cat()
else -> throw IllegalArgumentException("Unknown animal")
}
}
11. Best Practices & Tips
Idiomatic Kotlin
// Use when for multiple conditions
fun describe(x: Any) = when(x) {
is String -> "String of length ${x.length}"
is Int -> "Integer: $x"
else -> "Unknown"
}
// Prefer immutability
val list = mutableListOf<Int>().apply {
add(1)
add(2)
}.toList() // Convert to immutable
// Use inline functions for performance
inline fun measureTime(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
println("Took ${System.currentTimeMillis() - start}ms")
}
Critical Interview Topics Explained
1. Null Safety - Kotlin's Killer Feature
// Types are non-null by default
var name: String = "John" // Cannot be null
name = null // Compilation error!
// Nullable types require ?
var nullableName: String? = "John"
nullableName = null // OK
// Safe call operator ?.
val length = nullableName?.length // Returns Int? (null if nullableName is null)
// Elvis operator ?: provides default
val len = nullableName?.length ?: 0 // Returns 0 if null
// Non-null assertion !! (use sparingly!)
val len2 = nullableName!!.length // Throws NPE if null
// Smart casting
if (nullableName != null) {
// nullableName is automatically cast to non-null String here
println(nullableName.length)
}
2. val vs var - Immutability Matters
| Aspect | val | var |
|---|---|---|
| Mutability | Read-only reference | Mutable reference |
| Reassignment | Cannot reassign | Can reassign |
| Object Mutability | Object itself can be mutable | Object itself can be mutable |
| Thread Safety | Reference is thread-safe | Not thread-safe |
| Best Practice | Prefer val | Use when needed |
val list1 = mutableListOf(1, 2) // Reference immutable
list1.add(3) // OK - object is mutable
list1 = mutableListOf(4, 5) // Error - cannot reassign
var list2 = mutableListOf(1, 2) // Reference mutable
list2.add(3) // OK
list2 = mutableListOf(4, 5) // OK - can reassign
3. Coroutines vs Threads - Modern Concurrency
| Aspect | Coroutines | Threads |
|---|---|---|
| Weight | Lightweight (1000s possible) | Heavy (limited by OS) |
| Memory | ~2KB per coroutine | ~1MB per thread |
| Creation Cost | Cheap | Expensive |
| Scheduling | Cooperative | Preemptive |
| Blocking | Suspends, doesn't block thread | Blocks thread |
| Debugging | Easier with structured concurrency | Complex |
| Cancellation | Built-in support | Manual handling |
4. Inline Functions - Performance Optimization
// Without inline: Creates function object (overhead)
fun regularHigherOrder(block: () -> Unit) {
block()
}
// With inline: No function object, code inserted at call site
inline fun inlinedHigherOrder(block: () -> Unit) {
block() // Code is copied to call site
}
// Benefits:
// 1. No allocation overhead for lambda
// 2. Allows non-local returns
// 3. Can use reified type parameters
inline fun <reified T> isInstance(value: Any): Boolean {
return value is T // Can check type at runtime
}
5. Sealed Classes - Type-Safe State Management
// All subclasses must be defined in same file (before Kotlin 1.5)
// or same package (Kotlin 1.5+)
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
}
// Exhaustive when expression (no else needed)
fun handleResult(result: Result<String>) {
when (result) {
is Result.Success -> println(result.data)
is Result.Error -> println(result.exception)
Result.Loading -> println("Loading...")
// Compiler knows all cases are covered
}
}
6. Equality in Kotlin
data class Person(val name: String, val age: Int)
val p1 = Person("John", 25)
val p2 = Person("John", 25)
val p3 = p1
// Structural equality (calls equals())
p1 == p2 // true - same content
p1 == p3 // true
// Referential equality (same object in memory)
p1 === p2 // false - different objects
p1 === p3 // true - same object
Memory & Performance
// Avoid creating unnecessary objects
// Bad
list.filter { it > 0 }.map { it * 2 }
// Good (use sequence for large collections)
list.asSequence()
.filter { it > 0 }
.map { it * 2 }
.toList()
// Use const for compile-time constants
companion object {
const val MAX_COUNT = 100 // Compile-time
val MIN_COUNT = 0 // Runtime
}
Performance & Best Practices
Collection Performance Tips
// Use sequences for large collections with multiple operations
val result = list
.asSequence() // Lazy evaluation
.filter { it > 0 }
.map { it * 2 }
.take(5)
.toList() // Terminal operation
// Use appropriate collection types
val readOnlyList: List<Int> = listOf(1, 2, 3) // Immutable
val mutableList: MutableList<Int> = mutableListOf(1, 2, 3)
val arrayList: ArrayList<Int> = arrayListOf(1, 2, 3) // When you need ArrayList specifically
// For small maps with Int keys
val sparseArray = SparseArray<String>() // More memory efficient than HashMap
Android-Specific Best Practices
- Use Parcelize for Parcelable:
@Parcelize
data class User(val name: String, val age: Int) : Parcelable
- Scope Functions for View Binding:
binding.apply {
userName.text = user.name
userAge.text = user.age.toString()
submitButton.setOnClickListener { submit() }
}
- Extension Functions for Cleaner Code:
fun View.show() { visibility = View.VISIBLE }
fun View.hide() { visibility = View.GONE }
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
Quick Reference
Essential Keywords
- val/var: Immutable/mutable variables
- fun: Function declaration
- class/interface: Type declarations
- object: Singleton/anonymous object
- companion object: Static members
- data class: Auto-generated methods
- sealed class: Restricted hierarchy
- open/final: Inheritance control
- override: Method overriding
- inline/crossinline/noinline: Inline function modifiers
- reified: Type parameters in inline functions
- suspend: Coroutine functions
- by: Delegation
Operators
- ?: Nullable type
- ?.: Safe call
- ?:: Elvis operator
- !!: Non-null assertion
- as/as?: Cast/safe cast
- in/!in: Contains check
- ..: Range
- ::: Reference operator
Interview Success Strategy
Top 10 Must-Know Concepts
- Null Safety - Safe calls, Elvis operator, smart casting
- Coroutines - launch, async/await, scopes, cancellation
- Collections - List vs MutableList, map/filter/reduce, sequences
- Scope Functions - let, run, with, apply, also
- Extension Functions - How to create and when to use
- Higher-Order Functions - Functions as parameters, lambdas
- Sealed Classes - When expressions, state management
- Data Classes - equals, hashCode, copy, destructuring
- Delegation - by lazy, observable, class delegation
- Generics - Variance (in, out), reified parameters
Common Coding Challenges
- Implement a thread-safe singleton
- Write a custom delegation property
- Create a DSL using lambdas with receivers
- Implement a generic repository pattern
- Build a coroutine-based retry mechanism
- Design a sealed class hierarchy for API responses
Red Flags to Avoid
- Overusing !! (non-null assertion)
- Not handling null cases properly
- Blocking main thread with runBlocking
- Creating unnecessary companion objects
- Using var when val would work
- Ignoring scope function conventions
- Not leveraging Kotlin's standard library
Pro Tip: In interviews, always explain your choice of Kotlin features. Show that you understand not just the syntax but the why behind using specific language features. Demonstrate how Kotlin's features solve real problems and improve code quality, safety, and maintainability.