Post

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.

Kotlin Introduction

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 Im %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
}
This post is licensed under CC BY 4.0 by the author.