Kotlin Introduction
Kotlin is a cross-platform, statically typed, general-purpose high-level programming language with type inference. Kotlin is designed to interoperate fully with Java.
Basics
var
and val
The difference between var
and val
is that variables declared with the var
keyword can be changed/modified, while val
variables cannot.
Variable Type
Unlike many other programming languages, variables in Kotlin do not need to be declared with a specified type (like “String” for text or “Int” for numbers).
1
2
var name = "John"
val birthyear = 1975
However, it is possible to specify the type:
1
2
var name: String = "John"
val birthyear: Int = 1975
Type Conversion
Type conversion is when you convert the value of one data type to another type. To convert a numeric data type to another type, you must use one of the following functions: toByte()
, toShort()
, toInt()
, toLong()
, toFloat()
, toDouble()
or toChar()
Example
1
2
val x: Int = 5
val y: Long = x.toLong()
Standard Input
1
2
var input = readLine()
print("You entered: $input")
String
A String in Kotlin is an object, which contain properties and functions that can perform certain operations on strings, by writing a dot character (.) after the specific string variable.
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
fun main() {
val str = "Hello, Kotlin!"
// Length
println("Length: ${str.length}") // 13
// Access characters
println("Char at 1: ${str[1]}") // e
// Case conversion
println("Lowercase: ${str.lowercase()}") // hello, kotlin!
println("Uppercase: ${str.uppercase()}") // HELLO, KOTLIN!
// Trim
val spaced = " Kotlin "
println("Trim: '${spaced.trim()}'")
println("Trim Start: '${spaced.trimStart()}'")
println("Trim End: '${spaced.trimEnd()}'")
// Substring
println("Substring from 7: ${str.substring(7)}") // Kotlin!
println("Substring 0-5: ${str.substring(0, 5)}") // Hello
// Contains
println("Contains 'Kot': ${str.contains("Kot")}") // true
// StartsWith / EndsWith
println("Starts with 'Hel': ${str.startsWith("Hel")}") // true
println("Ends with '!': ${str.endsWith("!")}") // true
// IndexOf / LastIndexOf
println("Index of 'o': ${str.indexOf('o')}") // 4
println("Last index of 'o': ${str.lastIndexOf('o')}") // 9
// Replace / ReplaceFirst
println("Replace: ${str.replace("Kotlin", "World")}") // Hello, World!
println("Replace First: ${"one one one".replaceFirst("one", "1")}") // 1 one one
// Split
val parts = "a,b,c".split(",")
println("Split: $parts") // [a, b, c]
// JoinToString
val list = listOf("a", "b", "c")
println("JoinToString: ${list.joinToString(",")}") // a,b,c
// Repeat
println("Repeat: ${"Hi ".repeat(3)}") // Hi Hi Hi
// Reverse
println("Reversed: ${"abc".reversed()}") // cba
// isEmpty / isNotEmpty
println("Is empty: ${"".isEmpty()}") // true
println("Is not empty: ${"abc".isNotEmpty()}") // true
// isBlank / isNotBlank
println("Is blank: ${" ".isBlank()}") // true
println("Is not blank: ${" abc".isNotBlank()}") // true
// compareTo
println("CompareTo: ${"apple".compareTo("banana")}") // < 0
// plus
println("Plus: ${"Kotlin" + "Lang"}") // KotlinLang
// padStart / padEnd
println("Pad Start: ${"42".padStart(5, '0')}") // 00042
println("Pad End: ${"42".padEnd(5, '*')}") // 42***
// removePrefix / removeSuffix
println("Remove Prefix: ${"unhappy".removePrefix("un")}") // happy
println("Remove Suffix: ${"file.txt".removeSuffix(".txt")}") // file
// drop / dropLast
println("Drop 2: ${"Kotlin".drop(2)}") // tlin
println("DropLast 2: ${"Kotlin".dropLast(2)}") // Kotl
// take / takeLast
println("Take 3: ${"Kotlin".take(3)}") // Kot
println("TakeLast 3: ${"Kotlin".takeLast(3)}") // lin
// Capitalize / Decapitalize (deprecated, use replaceFirstChar)
println("Capitalize: ${"hello".replaceFirstChar { it.uppercase() }}") // Hello
println("Decapitalize: ${"HELLO".replaceFirstChar { it.lowercase() }}") // hELLO
// Format
val name = "Tawhid"
val age = 21
println("Format: ${"My name is %s and I’m %d years old".format(name, age)}")
// toCharArray
val chars = "Kotlin".toCharArray()
println("ToCharArray: ${chars.joinToString()}") // K, o, t, l, i, n
// Regex: matches, replace with Regex
println("Matches \\d+: ${"1234".matches(Regex("\\d+"))}") // true
println("Replace digits with #: ${"abc123".replace(Regex("\\d+"), "#")}") // abc#
// Extra: also, let, run (higher-order extension functions)
"Kotlin".also { println("Also: $it") }
"Kotlin".let { println("Let: Length is ${it.length}") }
"Kotlin".run {
println("Run: First letter is ${this[0]}")
}
}
If..Else Expressions
In Kotlin, you can also use if..else statements as expressions (assign a value to a variable and return it)
Example
1
2
3
4
5
6
val time = 20
val greeting = if (time < 18) {
"Good day."
} else {
"Good evening."
}
When
Instead of writing many if..else expressions, you can use the when expression, which is much easier to read.
1
2
3
4
5
6
7
8
9
10
11
val day = 4
val result = when (day) {
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
7 -> "Sunday"
else -> "Invalid day."
}
Loop
Kotlin supports several types of loops to perform repetitive tasks:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
fun main() {
// 1. for loop with range
for (i in 1..5) {
println("for loop (1..5): $i")
}
// 2. for loop with list
val items = listOf("A", "B", "C")
for (item in items) {
println("for loop (list): $item")
}
// 3. while loop
var x = 1
while (x <= 3) {
println("while loop: $x")
x++
}
// 4. do-while loop
var y = 1
do {
println("do-while loop: $y")
y++
} while (y <= 3)
// 5. forEach loop
val numbers = listOf(10, 20, 30)
numbers.forEach {
println("forEach loop: $it")
}
// 6. forEachIndexed loop
val letters = listOf("X", "Y", "Z")
letters.forEachIndexed { index, value ->
println("forEachIndexed: index=$index, value=$value")
}
}
Array
Arrays are used to store multiple values in a single variable, instead of creating separate variables for each value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
fun main() {
// Create arrays
val nums = arrayOf(1, 2, 3, 4, 5)
val letters = arrayOf("a", "b", "c")
// Size
println("Size: ${nums.size}") // 5
// Access and modify elements
println("First element: ${nums[0]}")
nums[0] = 10
println("Modified first element: ${nums[0]}")
// Get / Set
println("Get at 2: ${nums.get(2)}")
nums.set(2, 99)
println("Set index 2 to 99: ${nums[2]}")
// First / Last
println("First: ${nums.first()}")
println("Last: ${nums.last()}")
// IndexOf / LastIndexOf
println("Index of 99: ${nums.indexOf(99)}")
println("Last index of 99: ${nums.lastIndexOf(99)}")
// Contains / In
println("Contains 4: ${nums.contains(4)}")
println("3 in nums: ${3 in nums}") // false (we replaced 3 with 99)
// isEmpty / isNotEmpty
println("Is empty: ${nums.isEmpty()}")
println("Is not empty: ${nums.isNotEmpty()}")
// JoinToString
println("JoinToString: ${nums.joinToString()}") // 10, 2, 99, 4, 5
// forEach
nums.forEach { println("Element: $it") }
// forEachIndexed
nums.forEachIndexed { index, value -> println("Index $index: $value") }
// map
val doubled = nums.map { it * 2 }
println("Doubled: $doubled")
// filter
val even = nums.filter { it % 2 == 0 }
println("Even numbers: $even")
// any / all / none
println("Any > 10: ${nums.any { it > 10 }}")
println("All > 0: ${nums.all { it > 0 }}")
println("None < 0: ${nums.none { it < 0 }}")
// count
println("Count > 5: ${nums.count { it > 5 }}")
// find / findLast
println("Find > 10: ${nums.find { it > 10 }}")
println("FindLast > 10: ${nums.findLast { it > 10 }}")
// sum / average / max / min
println("Sum: ${nums.sum()}")
println("Average: ${nums.average()}")
println("Max: ${nums.maxOrNull()}")
println("Min: ${nums.minOrNull()}")
// take / takeLast
println("Take 3: ${nums.take(3)}")
println("TakeLast 3: ${nums.takeLast(3)}")
// drop / dropLast
println("Drop 2: ${nums.drop(2)}")
println("DropLast 2: ${nums.dropLast(2)}")
// reversed
println("Reversed: ${nums.reversed()}")
// sorted
println("Sorted: ${nums.sorted()}")
println("Sorted descending: ${nums.sortedDescending()}")
// copyOf / copyOfRange
val copied = nums.copyOf()
println("CopyOf: ${copied.joinToString()}")
val rangeCopy = nums.copyOfRange(1, 4)
println("CopyOfRange 1-4: ${rangeCopy.joinToString()}")
// fill
val filled = Array(5) { 0 }
filled.fill(7)
println("Filled with 7: ${filled.joinToString()}")
// withIndex
for ((index, value) in nums.withIndex()) {
println("WithIndex - $index: $value")
}
// toList
val list = nums.toList()
println("ToList: $list")
// contentEquals / contentDeepEquals
val arr1 = arrayOf(1, 2, 3)
val arr2 = arrayOf(1, 2, 3)
println("Content Equals: ${arr1.contentEquals(arr2)}")
// contentToString
println("ContentToString: ${arr1.contentToString()}")
// multidimensional arrays
val matrix = arrayOf(
arrayOf(1, 2),
arrayOf(3, 4)
)
println("2D Access: ${matrix[1][1]}") // 4
// contentDeepToString
println("Deep ToString: ${matrix.contentDeepToString()}")
// distinct
val dupArray = arrayOf(1, 2, 2, 3, 3, 3)
println("Distinct: ${dupArray.distinct()}") // [1, 2, 3]
// zip
val a = arrayOf(1, 2, 3)
val b = arrayOf("a", "b", "c")
val zipped = a.zip(b)
println("Zipped: $zipped") // [(1, a), (2, b), (3, c)]
// unzip
val (unzippedA, unzippedB) = zipped.unzip()
println("Unzipped A: $unzippedA")
println("Unzipped B: $unzippedB")
}
List
A List in Kotlin is an ordered collection of elements. It can be read-only (List
) or mutable (MutableList
).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fun main() {
// Read-only List
val fruits = listOf("Apple", "Banana", "Mango")
println(fruits[0]) // Output: Apple
println(fruits.size) // Output: 3
println("Banana" in fruits) // Output: true
for (fruit in fruits) {
println(fruit)
}
// Mutable List
val numbers = mutableListOf(1, 2, 3)
numbers.add(4)
numbers.remove(2)
numbers[0] = 10
println(numbers) // Output: [10, 3, 4]
}
List Vs Array
In Kotlin, a List is a collection that can be either read-only (List
) or modifiable (MutableList
), and it offers rich functions like map
, filter
, and forEach
. An Array is a fixed-size collection of elements that allows direct access and modification using indices. Lists are generally preferred for flexible and functional programming, while arrays are better suited for fixed-size, performance-critical tasks.
Ranges
With the for loop, you can also create ranges of values with ".."
1
2
3
for (nums in 5..15) {
println(nums)
}
The first and last value is included in the range.
Functions
A function is a block of code which only runs when it is called. You can pass data, known as parameters, into a function. Functions are used to perform certain actions, and they are also known as methods.
1
2
3
4
5
6
7
8
9
10
fun sum(x: Int, y: Int) = x + y
fun myFunction(x: Int, y: Int): Int {
return (x + y)
}
fun main() {
var result = myFunction(3, 5)
println(result) //8 (3 + 5)
}
Extension Functions
Extension functions allow adding new functions to existing classes without modifying their source code. This is useful for enhancing classes with additional utility methods, especially for standard types like String
, Int
, List
, etc.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Extension function on String
fun String.firstChar(): Char {
return this[0]
}
// Extension function on Int
fun Int.square(): Int {
return this * this
}
fun main() {
val name = "Tawhid"
val num = 5
println(name.firstChar()) // Output: T
println(num.square()) // Output: 25
}
Lambda Functions
Lambda functions are anonymous functions that can be treated as values—passed as arguments, stored in variables, or returned from functions. They are widely used for functional programming and in APIs
like map
, filter
, and forEach
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
val sum: (Int, Int) -> Int = { x, y -> x + y }
val greet: () -> String = { "Hello, Kotlin!" }
// Lambda passed as a function argument
fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
fun main() {
println(sum(3, 4)) // Output: 7
println(greet()) // Output: Hello, Kotlin!
val sumResult = operateOnNumbers(10, 5) { x, y -> x + y }
println("Sum: $sumResult") // Output: Sum: 15
val diffResult = operateOnNumbers(10, 5) { x, y -> x - y }
println("Difference: $diffResult") // Output: Difference: 5
}
Null Safety
Kotlin’s null safety feature helps to eliminate the risk of NullPointerException by distinguishing between nullable and non-nullable types.
- Nullable types (?)
- Safe call operator (?.)
- Elvis operator (?:)
- Not-null assertion (!!)
- let with safe calls
Examples
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fun main() {
// Nullable type
val name: String? = getNameFromDatabase()
// Safe call - won't crash if name is null
println("Name length: ${name?.length}")
// Elvis operator - provides default if null
val displayName = name ?: "Guest"
println("Hello, $displayName!")
// Safe call with let
name?.let {
println("Name in uppercase: ${it.uppercase()}")
}
// Not-null assertion - throws exception if null
try {
val forcedName = name!!
println("Forced name: $forcedName")
} catch (e: KotlinNullPointerException) {
println("Caught KotlinNullPointerException: name was null!")
}
}
// This function simulates returning a nullable String
fun getNameFromDatabase(): String? {
return null // or try returning "Tawhid"
}
Explanation
- String? means it can be null.
- name?.length safely accesses length if name is not null.
- name ?: “Guest” means “use name if not null, else use ‘Guest’”.
- name?.let { … } executes the block only if name is not null.
- name!! forces access and will crash if name is null.
Try/Catch
Try/Catch is used to handle exceptions (errors) in Kotlin. Code that might throw an exception is written inside the try
block. If an exception occurs, the catch
block handles it. Optionally, a finally
block can be used to execute code regardless of whether an exception was thrown or not.
1
2
3
4
5
6
7
8
9
10
11
fun main() {
try {
println("Opening file...")
// simulate error
throw Exception("File not found")
} catch (e: Exception) {
println("Caught exception: ${e.message}")
} finally {
println("Closing file...")
}
}
Classes
A class is a blueprint for creating objects. It can contain properties (variables) and functions (methods) that define the behavior and state of the object.
1
2
3
4
5
6
7
8
9
10
11
12
13
// Defining a class
class Person(val name: String, var age: Int) {
fun greet() {
println("Hello, my name is $name and I am $age years old.")
}
}
fun main() {
val person1 = Person("Tawhid", 22)
person1.greet() // Output: Hello, my name is Tawhid and I am 22 years old.
}
Data Class
A data class is a special class in Kotlin used to hold data. It automatically provides useful functions such as toString()
, equals()
, hashCode()
, and copy()
. Declared using the data
keyword.
1
2
3
4
5
6
7
8
9
10
11
12
13
// Defining a data class
data class User(val name: String, val age: Int)
fun main() {
val user1 = User("Tawhid", 22)
val user2 = User("Tawhid", 22)
println(user1) // Output: User(name=Tawhid, age=22)
println(user1 == user2) // Output: true (checks value equality)
val user3 = user1.copy(age = 23)
println(user3) // Output: User(name=Tawhid, age=23)
}
Open Class
By default, classes in Kotlin are final—they cannot be inherited. To allow a class to be subclassed, it must be marked with the open keyword.
1
2
3
4
5
6
7
8
9
10
11
12
13
open class Animal {
open fun sound() {
println("Some sound")
}
}
class Dog : Animal() {
override fun sound() {
println("Bark")
}
}
Sealed Class
A sealed class is a special kind of class that restricts subclassing—all subclasses must be defined in the same file. It is used to represent a fixed set of types, often for handling states or results in a type-safe way.
1
2
3
4
5
6
7
8
9
10
11
12
13
sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
fun handleResult(result: Result) {
when (result) {
is Success -> println("Data: ${result.data}")
is Error -> println("Error: ${result.message}")
is Loading -> println("Loading...")
}
}
Enum Class
An enum class in Kotlin is a special class used to represent a fixed set of constants. Each constant is an object, and enum classes can also have properties and functions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
enum class Day(val isWeekend: Boolean) {
MONDAY(false),
FRIDAY(false),
SATURDAY(true),
SUNDAY(true);
fun info() = if (isWeekend) "Weekend" else "Weekday"
}
fun main() {
println(Day.SATURDAY.info()) // Output: Weekend
val dir = Direction.NORTH
println(dir) // Output: NORTH
println(dir.name) // Output: NORTH
println(dir.ordinal) // Output: 0
}
Interfaces
An interface in Kotlin defines a contract that classes can implement. It can contain abstract methods, default method implementations, and properties (without backing fields). Interfaces do not store state.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Animal {
fun makeSound()
fun eat() {
println("Animal is eating")
}
}
class Dog : Animal {
override fun makeSound() {
println("Bark")
}
}
fun main() {
val dog = Dog()
dog.makeSound() // Output: Bark
dog.eat() // Output: Animal is eating
}
Sealed Interface
A sealed interface is an interface that restricts which classes can implement it. All implementations must be declared in the same file. This helps the compiler know all possible types at compile-time—useful for when expressions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
sealed interface Result
data class Success(val data: String) : Result
data class Error(val message: String) : Result
object Loading : Result
fun handleResult(result: Result) {
when (result) {
is Success -> println("Data: ${result.data}")
is Error -> println("Error: ${result.message}")
is Loading -> println("Loading...")
}
}
This code defines a sealed interface Result
to represent different outcomes of an operation. Three types implement it: Success
holds data, Error
holds an error message, and Loading
represents a loading state. The handleResult
function uses a when
expression to handle each type specifically. Because Result
is sealed, the compiler knows all possible types, making the when
exhaustive and safe.
Sealed Class vs Sealed Interface
In Kotlin, both sealed classes and sealed interfaces are used to restrict a type hierarchy to a fixed set of implementations defined in the same file, enabling safe and exhaustive when expressions. A sealed class can hold state, have constructors, and include common logic, making it suitable for representing structured data models or state hierarchies. A sealed interface cannot hold state or constructors but allows a class to implement multiple sealed interfaces, making it better for representing capabilities or behaviors across types.
Abstract
The abstract
keyword is used to define classes or methods that are incomplete and meant to be overridden in subclasses. An abstract class cannot be instantiated, and an abstract method has no body.
Abstract Method
An abstract method is a method declared without a body inside an abstract class or interface. It must be overridden in any non-abstract subclass or implementing class.
1
2
3
4
5
6
7
8
9
abstract class Animal {
abstract fun makeSound() // Abstract method (no body)
}
class Dog : Animal() {
override fun makeSound() {
println("Bark")
}
}
Abstract Class
An abstract class is a class that cannot be instantiated on its own. It can contain abstract members (without implementation) as well as concrete members (with implementation). Used as a base class for other classes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Animal {
abstract fun makeSound()
fun sleep() {
println("Sleeping...")
}
}
class Cat : Animal() {
override fun makeSound() {
println("Meow")
}
}
fun main() {
val cat = Cat()
cat.makeSound() // Output: Meow
cat.sleep() // Output: Sleeping...
}
Interface vs Abstract Class
In Kotlin, interfaces define a set of actions that a class can perform and support multiple inheritance, but they can’t store data. Abstract classes can have both incomplete and complete methods, store data, and include constructors, but only one can be inherited. Use interfaces for behavior, and abstract classes for shared structure.
Singletons
A singleton is a design pattern where only one instance of a class exists throughout the application. In Kotlin
, this is easily implemented using the object keyword.
1
2
3
4
5
6
7
8
9
10
object Logger {
fun log(message: String) {
println("Log: $message")
}
}
fun main() {
Logger.log("App started") // Output: Log: App started
}
In Kotlin, a singleton is created using the object
keyword, which ensures only one instance exists. It is ideal for global access points like loggers, configuration managers, or utility classes. Singletons are initialized lazily and are thread-safe by default.
Visibility Modifiers
public
: Default. Visible everywhere.internal
: Visible within the same module.protected
: Visible to the class and its subclasses (only in classes).private
: Visible only inside the file or class where it is declared.
1
2
3
4
5
6
7
8
class MyClass {
private val secret = "Hidden"
internal val moduleValue = "Module Access"
protected val inheritedValue = "Subclass Access"
public val everyoneCanSee = "Public"
}
Generics
Generics allow writing flexible and reusable code by working with types as parameters. They enable classes, functions, and interfaces to operate on different data types without rewriting code for each one.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Box<T>(val content: T) {
fun getContent(): T {
return content
}
}
fun main() {
val intBox = Box(123)
val stringBox = Box("Hello")
println(intBox.getContent()) // Output: 123
println(stringBox.getContent()) // Output: Hello
}