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, Boopathilambda 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
infixkeyword
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 //BobHere, 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
initblock 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:
this(id, name)calls the primary constructor, which triggers theinitblock.- After the
initblock 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 objectis 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