Introduction to Kotlin
Kotlin is a statically typed, cross-platform programming language developed by JetBrains. It was
created by Dmitry Jemerov and Andrey Breslav and officially released in 2011. Kotlin is renowned for
its simplicity, efficiency, and seamless interoperability with Java, making it a popular choice for
modern Android development and enterprise applications. Its clean syntax, null safety features, and
extensive standard library contribute to its growing popularity among developers, particularly in
mobile development, server-side applications, and data science.
Table of Contents
Junior-Level Kotlin Interview Questions
Here are some junior-level interview questions for Kotlin:
Question 01: What are the main features of Kotlin?
Answer: Kotlin is a statically typed programming language developed by JetBrains, designed to
interoperate fully with Java. It has gained popularity due to its expressive syntax, safety
features, and modern design. Here are the main features of Kotlin:
- One of Kotlin's most notable features is its null safety system, which helps to eliminate the
NullPointerException by differentiating nullable and non-nullable types at compile-time.
- Kotlin is designed to be fully interoperable with Java, allowing developers to use Kotlin code
alongside Java code seamlessly.
- Kotlin provides built-in support for coroutines, which are used for asynchronous programming.
- Kotlin includes smart casts, which automatically handle type casting, reducing the need for
explicit casts.
- Kotlin allows developers to add new functions to existing classes without modifying their
source code, known as extension functions.
Question 02: What is the purpose of the companion object in Kotlin?
Answer: The companion object in Kotlin is used to define static-like methods and properties
that belong to the class rather than instances, enabling class-level functionality such as utility
methods or factory functions. For
example:
class MyClass {
companion object {
const val CONSTANT = "Some constant value"
fun staticMethod() {
println("This is a static-like method")
}
}
}
// Accessing companion object members
println(MyClass.CONSTANT) // Output: Some constant value
MyClass.staticMethod() // Output: This is a static-like method
Question 03: Explain the use of the lateinit keyword in Kotlin.
Answer: The lateinit keyword in Kotlin is used to declare a non-nullable property that will be
initialized later. It allows you to avoid null checks and initialization in the constructor,
typically for properties that are set up after the object is created. For example:
class User {
lateinit var name: String
}
fun main() {
val user = User()
user.name = "Alice" // Initializing the property later
println(user.name) // Output: Alice
}
Question 04: What will be the output of the following code?
fun main() {
val list = listOf(1, 2, 3, 4, 5)
val result = list.filter { it % 2 == 0 }.map { it * 2 }
println(result)
}
Answer: The output will be [4, 8].
Question 05: What is a data class in Kotlin?
Answer:
In Kotlin, a data class is a special class designed to hold data with minimal boilerplate code. Data
classes automatically generate common methods like toString(), equals(), hashCode(), and copy(),
based on the properties defined in the primary constructor. This feature simplifies the creation of
classes whose main purpose is to store data, making code more concise and readable.
A data class is defined using the data keyword before the class name, and it must have at least one
property in the primary constructor. For example, data class User(val name: String, val age: Int)
creates a class where name and age are properties, and Kotlin generates the necessary methods for
comparison and copying. Data classes also support destructuring declarations, allowing you to unpack
data into separate variables easily.
Question 06: Find the error in below code.
fun main() {
val name: String = null
println(name.length)
}
Answer: The variable name is declared as non-nullable, but it is assigned a null value, which
causes a compilation error. It should be declared as val name: String? = null.
fun main() {
val name: String? = null
println(name?.length ?: "Name is null")
}
Question 07: What are extension functions in Kotlin?
Answer: Extension functions in Kotlin allow you to add new methods or properties to existing
classes without modifying their source code. This feature is useful for adding functionality to
classes from libraries or third-party code. For
example:
// Extension function to add a greeting method to the String class
fun String.greet() {
println("Hello, $this!")
}
fun main() {
"World".greet() // Output: Hello, World!
}
In this example, fun String.greet() defines an extension function for the String class, allowing you to
call greet() on any String object to print a greeting message.
Question 08: What is a higher-order function in Kotlin?
Answer: A higher-order function in Kotlin is a function that takes other functions as
parameters or returns a function as a result. This feature enables functional programming techniques
like callbacks, and transformations.
For
example:
// Higher-order function that takes another function as a parameter
fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
fun main() {
val sum = operateOnNumbers(3, 4) { x, y -> x + y } // Passing a lambda function
println(sum) // Output: 7
}
In this example, operateOnNumbers is a higher-order function that takes another function as a
parameter (operation). It performs the operation on two integers and returns the result. The lambda { x,
y -> x + y } is passed to operateOnNumbers to compute the sum of 3 and 4.
Question 09: What are lambdas in Kotlin?
Answer: In Kotlin, lambdas are anonymous functions that allow you to create functions without
naming them. They are a key feature of Kotlin’s functional programming capabilities, enabling you to
write more concise and expressive code. Lambdas can be used in various contexts, such as passing
functions as arguments, defining inline functions, or creating custom collection operations.
A lambda expression in Kotlin is defined using curly braces {} and can capture variables from its
surrounding context. For example, { x: Int, y: Int -> x + y } is a lambda that takes two integers
and returns their sum. Lambdas are commonly used with collection functions like map, filter, and
reduce, allowing you to perform operations on data in a declarative way.
Question 10: What will be the output of the following code?
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val result = numbers.fold(0) { sum, element -> sum + element }
println(result)
}
Answer: The output will be 15.
Mid-Level Kotlin Interview Questions
Here are some mid-level interview questions for Kotlin:
Question 01: What is the difference between var and val in Kotlin?
Answer: In Kotlin, var is used for mutable variables whose values can be reassigned, while val
is used for immutable variables whose values cannot be changed after initialization. For example,
var age = 30; age = 35 is valid because var allows reassignment, whereas val name = "Alice"; name =
"Bob" will cause a compilation error because val creates a read-only reference. Use var when you
need to update a variable's value and val when the value should remain constant.For example:
var mutableVar = 10
val immutableVal = 20
mutableVar = 15 // This is allowed
immutableVal = 25 // This will cause a compilation error
Question 02: What will be the output of the following Kotlin code?
fun main() {
val x = 10
val y = 5
println(x / y * y)
}
Answer: The output will be 10. This is because integer division truncates the result.
Question 03: Explain the use of when expression in Kotlin.
Answer: The when expression in Kotlin is a powerful tool for conditional branching and is more
flexible than the traditional switch statement found in other languages. Unlike switch, which
handles only discrete values, when can work with a variety of conditions, including ranges,
collections, and complex expressions.
For example:
when (day) {
"Monday" -> println("Start of the work week")
"Saturday", "Sunday" -> println("Weekend")
in 1..5 -> println("Weekday")
else -> println("Invalid day")
}
In this example, when checks the value of day against several conditions, handling multiple cases,
ranges, and a default branch with else.
Question 04: Find the error in below Kotlin code.
fun printDetails(name: String, age: Int = 30) {
println("Name: $name, Age: $age")
}
printDetails("Alice", 25, "Extra Argument")
Answer: To fix the error, you should remove the extra argument "Extra Argument" from the
function call. The corrected code is:
fun printDetails(name: String, age: Int = 30) {
println("Name: $name, Age: $age")
}
printDetails("Alice", 25)
In this corrected code, the function printDetails is called with the correct number of arguments:
"Alice" for name and 25 for age.
Question 05: What is a sealed class in Kotlin?
Answer: A sealed class in Kotlin is a special kind of class used to represent a fixed set of
related types. It restricts class hierarchies to a specific set of subclasses, enabling exhaustive
when expressions and improving type safety. For example:
sealed class Result {
data class Success(val data: String) : Result()
object Error : Result()
}
fun handleResult(result: Result) {
when (result) {
is Result.Success -> println("Data: ${result.data}")
is Result.Error -> println("An error occurred")
}
}
Here, Result is a sealed class with Success and Error subclasses. The when expression covers all
subclasses, ensuring complete type handling.
Question 06: Explain inline functions in Kotlin.
Answer: Inline functions in Kotlin are functions whose code is directly inserted into the call
site, which improves performance by avoiding the overhead of function calls. For example:
inline fun greet(block: () -> Unit) {
println("Before")
block()
println("After")
}
fun main() {
greet { println("Hello") } // Lambda code is inlined here
}
Here, the greet function is inline, so the lambda { println("Hello") } is inserted into greet's
code, eliminating the function call overhead.
Question 07: What is the difference between HashMap and TreeMap in Kotlin?
Answer: In Kotlin, HashMap and TreeMap are two different implementations of the Map interface,
each with unique features and use cases. HashMap uses a hash table to store key-value pairs,
offering average constant-time complexity for operations like get and put. However, it does not
guarantee any specific order for the elements.
On the other hand, TreeMap uses a red-black tree to maintain a sorted order of keys. This allows
TreeMap to provide logarithmic time complexity for operations such as get and put. It also supports
operations that require sorted data. If you need elements to be
stored in a specific order or perform range queries, TreeMap is the better choice, while HashMap is
more efficient for general-purpose storage and retrieval of data without regard to order.
Question 08: What is the purpose of the @JvmOverloads annotation in Kotlin?
Answer: The @JvmOverloads annotation generates overloads for functions or constructors with
default parameters to ensure compatibility with Java.
For example:
class Person(val name: String, val age: Int = 30) {
@JvmOverloads
constructor(name: String) : this(name, 30)
}
Question 09: What are Kotlin’s let, run, with, apply, and also functions used for?
Answer: These are scope functions for different purposes:
- let: Executes code with the object and returns the result.
- run: Executes code with the object and returns the result.
- with: Executes code with the object and returns the result (object is not changed).
- apply: Executes code with the object and returns the object itself.
- also: Executes code with the object and returns the object itself.
Question 10: What will be the output of the following Kotlin code?
fun main() {
val str = "Kotlin"
val result = str.reversed()
println(result)
}
Answer: The output will be "niltok" because reversed() reverses the string.
Expert-Level Kotlin Interview Questions
Here are some expert-level interview questions for Kotlin:
Question 01: Explain the concept of coroutines in Kotlin.
Answer: Coroutines in Kotlin are lightweight, asynchronous units of work that simplify
managing concurrent operations. They allow you to write non-blocking, asynchronous code in a
sequential style, improving readability and performance for tasks like network requests or
background computations.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L) // Non-blocking delay for 1 second
println("World")
}
println("Hello")
}
In this example, launch creates a coroutine that runs concurrently with println("Hello"). The
delay(1000L) suspends the coroutine for 1 second without blocking the main thread, resulting in "Hello"
being printed before "World".
Question 02: How does Kotlin handle null safety?
Answer: Kotlin’s type system distinguishes nullable and non-nullable types. The safe call
operator ?. allows calling methods on a nullable type safely. For
example:
var name: String? = "Kotlin"
println(name?.length) // Output: 6
name = null
println(name?.length) // Output: null
Question 03: What are Kotlin’s delegation patterns? How do they work?
Answer: In Kotlin, delegation allows a class to pass the implementation of an interface to
another class, promoting code reuse and composition over inheritance. By using the by keyword, a
delegating class can forward interface methods and properties to a delegate object, making the code
more flexible and maintainable. For example:
class UserService(private val logger: Logger) : Logger by logger
Here, UserService delegates the Logger interface methods to the logger instance. This approach helps
avoid code duplication and supports the principle of composition over inheritance.
Question 04: What will be the output of the following Kotlin code?
fun main() {
val x = 10
val y = 5
val result = x.let { it * 2 }.takeIf { it > 10 }?.let { it / y }
println(result)
}
Answer: The output will be 4, as x.let { it * 2 } evaluates to 20, takeIf { it > 10 } keeps
20, and let { it / y } results in 4.
Question 05: What is the purpose of operator overloading in Kotlin?
Answer: Operator overloading in Kotlin allows you to define custom behavior for standard
operators like +, -, and * in your own classes. By implementing functions such as operator fun
plus(other: T), you can customize how these operators work with your class instances, making the
code more intuitive and expressive. For example:
For
example:
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point) = Point(x + other.x, y + other.y)
}
val p1 = Point(1, 2)
val p2 = Point(3, 4)
println(p1 + p2) // Output: Point(x=4, y=6)
Question 06: Discuss the compareAndSet method in Kotlin and its impact on performance.
Answer: compareAndSet is an atomic operation used in concurrent programming to ensure a
variable’s value changes only if it matches the expected value. It improves performance by avoiding
locking mechanisms. For example:
val atomicValue = AtomicInteger(0)
atomicValue.compareAndSet(0, 1) // Atomically sets value to 1 if current value is 0
Question 07: Explain the concept of type safe builders in Kotlin.
Answer: Type-safe builders in Kotlin enable the creation of complex objects using a fluent,
DSL-like (Domain-Specific Language) syntax that ensures compile-time safety and readability. These
builders leverage Kotlin's powerful features to provide a clean and expressive way to construct
complex hierarchies or configurations.
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html}
class HTML {
fun body(init: BODY.() -> Unit) { /* ... */ }
}
class BODY
html {
body {
// Configure body }
}
Question 08: What is the @Inject annotation in Kotlin used for?
Answer: The @Inject annotation in Kotlin is used for dependency injection in frameworks like
Dagger and Hilt. It marks constructors, fields, or methods to be automatically provided with
dependencies. This annotation simplifies dependency management and promotes loose coupling in
applications.
For example:
class Car @Inject constructor(private val engine: Engine) {
// Engine is provided by the DI framework
}
Here, @Inject tells the DI framework to provide an Engine instance when creating a Car object.
Question 09: How do you handle TimeoutException in Kotlin Coroutines?
Answer: In Kotlin Coroutines, TimeoutException is managed using withTimeout or
withTimeoutOrNull. The withTimeout function specifies a timeout period and throws a
TimeoutCancellationException if the coroutine takes too long, which you can catch and handle, while
withTimeoutOrNull returns null instead of throwing an exception if the timeout occurs.
For example:
import kotlinx.coroutines.*
suspend fun fetchData(): String? {
return withTimeoutOrNull(5000) { // 5000 ms timeout
delay(6000) // Simulate long task
"Data"
}
}
fun main() = runBlocking {
val result = fetchData()
if (result != null) {
println(result)
} else {
println("Operation timed out.") }
}
Here, withTimeoutOrNull returns null if the coroutine exceeds the timeout, allowing you to handle the
timeout scenario without using exceptions.
Question 10: What is the Nothing type in Kotlin, and when is it used?
Answer:
In Kotlin, the Nothing type represents a value that never occurs, used for functions or expressions that either throw an exception or run indefinitely. It signifies that a function does not complete normally and is a subtype of all other types, making it useful for indicating unreachable code, signaling errors, or modeling infinite loops.
For example:
fun fail(message: String): Nothing {
throw IllegalArgumentException(message) // Never returns
}
Here, fail throws an exception, so it has a return type of Nothing.
Ace Your Kotlin Interview: Proven Strategies and Best Practices
To excel in a Kotlin technical interview, it's crucial to have a strong grasp of the language's core
concepts. This includes a deep understanding of syntax and semantics, data types, and control
structures. Additionally, mastering Kotlin's approach to error handling is essential for writing robust
and reliable code. Understanding concurrency and parallelism can set you apart, as these skills are
highly valued in many programming languages.
- Core Language Concepts: Syntax, semantics, data types (built-in and composite), control
structures, and error handling.
- Concurrency and Parallelism: Creating and managing threads, using
communication mechanisms like channels and locks, and understanding synchronization primitives.
- Standard Library and Packages: Familiarity with the language's standard library and
commonly used packages, covering basic to advanced functionality.
- Practical Experience: Building and contributing to projects, solving real-world problems,
and showcasing hands-on experience with the language.
- Testing and Debugging: Writing unit, integration, and performance tests, and using
debugging tools and techniques specific to the language.
Practical experience is invaluable when preparing for a technical interview. Building and contributing
to projects, whether personal, open-source, or professional, helps solidify your understanding and
showcases your ability to apply theoretical knowledge to real-world problems. Additionally,
demonstrating your ability to effectively test and debug your applications can highlight your commitment
to code quality and robustness.