Kotlin is a statically-typed programming language that runs on the JVM (Java Virtual Machine), meaning it can work seamlessly with Java. It’s often used for Android app development as an alternative to Java. Kotlin is more modern, expressive, and safer than Java in many ways, which is why it has gained popularity in Android development. It also supports functional programming and object-oriented programming.

Variable

If we declare the variable using val, the reference to the object stays in the variable forever and can’t be replaced

Kotlin has a number of basic types: Byte, Short, Int, Long, Float, Double, Boolean, Char and String. All variable are objects

Collection

Kotlin collections are immutable by default (List, Set, Map) but can be made mutable using mutableListOf, mutableSetOf, etc.

 
var myArray = arrayOf(1, 2, 3)
val numbers = listOf(1, 2, 3) // Immutable
val names = mutableListOf("Tom", "Jerry")
 
names.add("Spike")
 
val items = setOf(1, 2, 2, 3) // [1, 2, 3]
val unique = mutableSetOf("A", "B")
unique.add("C")
 
 
val ages = mapOf("Alice" to 25, "Bob" to 30)
val scores = mutableMapOf("A" to 90)
scores["B"] = 85
 
val arr = arrayOf(1, 2, 3)
arr[0] = 10
 
/*
Everything is a function:
- listOf()
- setOf()
- mapOf("a" to 1)
- mutableListOf()
 
*/
  • Kotlin has intArrayOf, charArrayOf, etc., for performance
  • Unlike lists, arrays have a fixed size
  • Ambiguity {} in Kotlin means a lambda block)

When instead of swtich

val result = when (day) {
    "Mon" -> "Start"
    "Fri" -> "Party"
    else -> "Work"
}
 
when (day) {
    "Mon" -> {
        logDay("Starting the week")
        "Start"
    }
    "Fri" -> {
        logDay("Weekend ahead")
        "Party"
    }
    else -> {
        logDay("Regular day")
        "Work"
    }
}
 
fun logDay(msg: String) {
    println("LOG: $msg")
}
 

In Kotlin, all types are non-null by default:

val name: String = "Boopathi" // ✅
val name2: String = null      // ❌ Error
 
val name: String? = null // ✅ Nullable
 
//Runs the operation **only if the variable is not null**.
 
val name: String? = "Boopathi"
val length = name?.length // Returns Int? (nullable Int)
//If `name == null`, `length == null` — no crash.
 
//Not-null Assertion (`!!`)** 
 
//Forcefully unwraps a nullable value. Crashes if null.
 
val name: String? = null
val length = name!!.length  // ❌ Throws NullPointerException
 

Function

//Default & Named Parameters
fun greet(name: String = "Guest") {
    println("Hi, $name")
}
 
fun book(title: String, author: String) {}
 
book(author = "Tolstoy", title = "War & Peace")
 
fun square(n: Int) = n * n
 
//`Unit` is like `void` in Java (optional)
fun log(message: String): Unit {
    println(message)
}
 
 
//Add new functions to existing classes without modifying them.
 
fun String.hello(): String = "Hello, $this"
 
println("Boopathi".hello()) // Hello, Boopathi

lambda function

 
val upper: (String) -> String = { it.uppercase() }
 
val myLambda: (String, Int, Boolean) -> String = { name, age, isCool ->
    if (isCool) "$name is $age and cool!" else "$name is $age but not cool"
}
  • Lambda = { input -> output }
  • it = implicit name if one argument

infix function

An infix function is a fancy way to call functions without dots and parentheses.

To define a function as infix, it must:

  • Be a member or extension function
  • Have exactly one parameter
  • Be marked with the infix keyword
infix fun String.hello(to: String): String {
    return "Hello $to, I'm $this"
}
 
val msg = "Alice" hello "Bob"
// Instead of: "Alice".hello("Bob")
println(msg) // Hello Bob, I'm Alice
 

Try/catch

  • Kotlin does not force you to catch exceptions like Java does.
try {
    // code that might throw an exception
} catch (e: ExceptionType) {
    // handle exception
} finally {
    // optional: runs always
}
 
try {
    riskyOperation()
} catch (e: IOException) {
    println("IO error: ${e.message}")
} catch (e: NullPointerException) {
    println("Null error")
}
 
//custom exception
class InvalidInputException(message: String) : Exception(message)
 
fun validate(input: String) {
    if (input.isEmpty()) throw InvalidInputException("Input can't be empty")
}
 
 

runcatching

 
fun riskyOperation(): Int {
    if (Math.random() > 0.5) throw RuntimeException("Boom!")
    return 42
}
 
val result = runCatching {
    riskyOperation()
}.getOrElse {
    println("Caught exception: ${it.message}")
    -1
}
 
println("Result is $result")
 

OOPS

A class in Kotlin can hold properties and methods. It supports both default constructors and custom initialization through init blocks. Unlike Java, Kotlin simplifies class declarations:

class Person(val name: String, var age: Int)
 
val p = Person("Bob", 25)
p.name //Bob

Here, name is immutable (val), and age is mutable (var). These are automatically properties of the class. Kotlin generates the constructor and toString(), equals(), and hashCode() if the class is a data class.

Constructors and Initialization

You can define primary and secondary constructors in Kotlin.

  • The init block is used to write initialization logic that gets executed when the object is created.
class Employee(val id: Int, val name: String) {
    init {
        println("Created Employee: $name with ID $id")
    }
 
    constructor(id: Int, name: String, department: String) : this(id, name) {
        println("Assigned to department: $department")
    }
}

Execution Order:

  1. this(id, name) calls the primary constructor, which triggers the init block.
  2. After the init block finishes, the body of the secondary constructor runs —which prints the department.
  • The secondary constructor must delegate to the primary one using this(...). Kotlin doesn’t support constructor overloading like Java directly initialization logic is cleanly centralized.

Visibility and Encapsulation

Kotlin uses visibility modifiers to control access:

  • public: accessible everywhere.
  • private: visible only within the class or file.
  • protected: visible in class and subclasses.
  • internal: visible within the same module.

You can also make property access private and expose custom getters/setters:

class BankAccount {
    private var _balance: Double = 0.0
 
    var balance: Double
        get() = _balance
        private set(value) {
            if (value >= 0) _balance = value
        }
 
    fun deposit(amount: Double) {
        balance += amount
    }
}

In Kotlin, every var property automatically has:

  • A getter to retrieve the value.
  • A setter to modify the value.

Every val property only has:

  • A getter (no setter, because it’s read-only).
var name: String = "John"
 
//This is **automatically compiled into**:
 
private var _name: String = "John"
 
fun getName(): String = _name
 
fun setName(value: String) {
    _name = value
}
 

Inheritance and Method Overriding

Kotlin classes and methods are final by default. You must explicitly mark them as open to allow subclassing or overriding.

open class Vehicle {
    open fun startEngine() = println("Starting engine")
}
 
class Car : Vehicle() {
    override fun startEngine() = println("Car engine started")
}

You can also use super to refer to the superclass implementation when needed.

Abstract Classes and Interfaces

Abstract classes allow partially defined blueprints, whereas interfaces define complete contracts without state.

abstract class Shape {
    abstract fun area(): Double
    fun display() = println("Shape area is ${area()}")
}
 
class Circle(val radius: Double) : Shape() {
    override fun area() = Math.PI * radius * radius
}

Interfaces can contain default method implementations and properties (but without backing fields):

interface Logger {
    val tag: String
    fun log(msg: String) = println("[$tag]: $msg")
}

Multiple interfaces can be implemented, which allows powerful composition.

Companion Objects and Static Behavior

Kotlin has no static keyword. Instead, use a companion object for class-level functionality:

class AppConfig {
    companion object {
        fun load() = println("Configuration loaded")
    }
}
 
AppConfig.load()
  • companion object is Kotlin’s way of defining class-level members (like static in Java), but with object-oriented and functional programming powers.

  • It’s a real object that can hold state, implement interfaces, and access private members.

  • It’s compile-time safe, interoperable with Java, and flexible enough to express static logic without losing Kotlin’s design goals.

  • In kotilin everything is object so why they have designed like this

  • it’s actually a singleton object,

Singleton Objects

Use object to define a singleton, useful for stateless managers or global services:

object Logger {
    fun log(msg: String) = println("[LOG]: $msg")
}

Data Classes and Structural Equality

A data class is used to hold data. Kotlin automatically generates equals, hashCode, toString, and copy:

data class User(val id: Int, val name: String)
 
val u1 = User(1, "Alice")
val u2 = u1.copy(name = "Bob")

Useful for modeling DTOs, immutable entities, and business logic layers.


Sealed Classes for Restricted Hierarchies

A sealed class restricts class hierarchies at compile time. All subclasses must be declared in the same file.

sealed class Response
data class Success(val data: String) : Response()
data class Error(val message: String) : Response()
object Loading : Response()

This pattern is perfect for modeling state (UI, API responses, etc.) and can be safely handled with when expressions.


Inner and Nested Classes

Nested classes are static by default. Use the inner modifier to allow access to outer class members.

class Outer {
    private val secret = "Kotlin Rocks"
 
    inner class Inner {
        fun reveal() = println(secret)
    }
}

Nested classes without inner can’t access secret.

Object-Oriented Design Patterns in Kotlin

You can express many traditional design patterns more concisely in Kotlin. For example:

Factory Pattern:

interface Shape {
    fun draw()
}
 
class Circle : Shape {
    override fun draw() = println("Circle")
}
 
class ShapeFactory {
    companion object {
        fun getShape(type: String): Shape = when (type) {
            "circle" -> Circle()
            else -> throw IllegalArgumentException("Unknown type")
        }
    }
}

Strategy Pattern using Lambda:

class Printer(private val strategy: (String) -> Unit) {
    fun print(msg: String) = strategy(msg)
}
 
val upperPrinter = Printer { println(it.uppercase()) }

Kotlin makes patterns more concise thanks to lambdas, top-level functions, and smart typing.

Coroutine

A coroutine is a lightweight thread but unlike threads, coroutines:

  • Are cheap to create and run
  • Suspend instead of blocking
  • Resume from where they left off
  • Integrate directly with Kotlin’s syntax

Think of coroutines as functions that can pause and resume