// 24 Essential Kotlin Interview Questions
Kotlin is a JVM-based, open-source, general-purpose, statically typed programming language compatible with all current Java projects. It is used to make various applications, including server-side and Android apps. Moreover, It is fully compatible with Java and is widely considered a more advanced version of Java.
Kotlin combines functional programming with object-oriented programming (OOPs) to create a limitless, independent, and special platform. It also allows for functional twinning using microcodes, which makes it more and more well-liked by businesses. So if you are looking for a job in Kotlin, you'll need to prepare for a wide variety of challenging Kotlin interview questions. In this article, we shall look at the top questions generally asked during Kotlin Interviews. So let's get started.
Looking for Kotlin Developers? Build your product with Flexiple's dream talent.
Hire a Kotlin DeveloperHire NowKotlin is a simpler and cleaner programming language compared to Java. It significantly reduces code redundancy. The code is also more idiomatic thanks to Kotlin's useful features, which Java does not yet support. Recently, Kotlin was added to the list of languages supported by Android Studio. So, there is much to look forward to from Kotlin regarding future development efforts being made easier and receiving good support.
In Kotlin, we can easily create a singleton by using an object.
Syntax:
object ExampleSingleton
The above Kotlin object code will be compiled to the following equivalent Java code:
public final class ExampleSingleton {
public static final ExampleSingleton INSTANCE;
private ExampleSingleton() {
INSTANCE = (ExampleSingleton)this;
System.out.println("init complete");
}
static {
new ExampleSingleton();
}
}
This is the recommended way to implement singletons on a JVM. This is because the method enables thread-safe lazy initialization without any reliance on a locking algorithm.
Note: Commonly asked Kotlin interview questions.
One of the main benefits of using Kotlin is null safety. The type system in Kotlin ensures that the risk of null references in code, also known as The Billion Dollar Mistake, is eliminated.
Accessing a member with a null reference will cause a null reference exception, one of the most frequent pitfalls in many programming languages, including Java. This corresponds to a Java NullPointerException.
The type system in Kotlin can distinguish between references that can hold null and those that cannot. For example, a regular String type variable can not hold null:
var a: String = "some string"
a = null // compilation error
To allow null values, we declare a variable as a nullable string written in the following way:
var b: String? = "some string"
b = null // ok
print(b)
In Java, if you try to access some null variable, you will receive a NullPointerException. For example, the following code in Kotlin will produce a compile-time error:
var name: String = "MindOrks"
name = null //error
To get around this problem, you must assign null values to variables. To do this, you must declare the name variable as a nullable string and then access the variable using a safe call operator, i.e., ?.
var name: String? = "MindOrks"
print(name?.length) // ok
name = null // ok
The above code tries to declare a variable using lateinit initialization.
Lateinit means late initialization. This initialization method is used when you do not want to initialize a variable in the constructor but instead want to initialize it later.
To ensure initialization, you should declare that variable with the lateinit keyword rather than before using it. Until it is initialized, it will not allocate memory. Moreover, for primitive type properties like Int, Long, etc., you cannot use lateinit.
This is mainly used in:
- Android: For variables that get initialized inside lifecycle methods.
- Using Dagger for DI: For injected class variables that are initialized outside and are used independently from the constructor.
- Setup for unit tests: In the test environment, variables are initialized in a @Before - annotated method.
- Spring Boot annotations like @Autowired.
If a value is null, the Kotlin double-bang (!!) operator converts it to a non-null type and raises a KotlinNullPointerException exception. It is also known as the not-null assertion operator.
The !! operator should only be used when a developer is absolutely certain that the value of this operator will not be null.
The Data class is a streamlined class offering common functions and storing data. A class can be declared as a data class in Kotlin using the designated "data" keyword.
Syntax:
data class SomeClassName ( list_of_parameters)The compiler for the data classes derives the following functions automatically:
- equals() - This returns true if two objects have identical contents. It operates quite similarly to the double equals "==" symbol to compare two values. However, this function works differently for Float and Double values.
- hashCode() - This function returns the hashcode value of the object.
- copy() - The function duplicates the object's contents, changing a few characteristics, like hashcode, while leaving the rest unaltered.
- toString() - The function returns a string containing all of the data class's parameters.
However, to ensure consistency amongst data classes, all data classes must meet the following requirements:
- The primary constructor for the data class must have at least one parameter.
- The val or var must be used for all primary constructor parameters.
- Abstract, open, sealed, or inner data classes are impossible.
- Data classes can only implement interfaces.
Example:
data class Example(var input1 : Int, var input2 : Int)
The code snippet above creates a data class ‘Example’ with two parameters.
fun main(agrs: Array<String>) {
val temp = Example(1, 2)
println(temp)
}
In this code snippet, we create an instance of the data class ‘Example’ and pass the parameters to it.
Output:
Example(input1=1, input2=2)
Safe Call operator ( ?. ) - Null comparisons are simple, but the sheer volume of nested if-else statements can become tiresome. Consequently, the Safe call operator (?.), available in Kotlin, simplifies matters by only performing an action when a specified reference contains a non-null value. It enables us to execute a method call and a null check using a single expression.
For example, the following expression in Kotlin
name?.toLowerCase()
is equivalent to the following
if(name != null)
name.toLowerCase()
else
null
Elvis Operator ( ?: ) - The Elvis operator is used to return a non-null value or a default value when the initial variable is null. In other words, the Elvis operator yields the right expression if the left expression is null and returns the left expression otherwise. The right-hand side is only evaluated if the left-hand side expression is null.
For example,
The following expression in Kotlin
val sample1 = sample2 ?: "Undefined"
is equivalent to the following
val sample1 = if(sample2 != null)
sample2
else
"Undefined"
Additionally, we can use throw and return expressions on the right side of the Elvis operator, which is useful in functions. As a result, on the right side of the Elvis operator, we can throw an exception rather than returning a default value. For instance,
val sample1 = sample2 ?: throw IllegalArgumentException("Invalid")
Note: Commonly asked Kotlin interview questions.
There are two types of Kotlin constructors:
-
Primary Constructor - The primary constructor is initialized in a class's header and provided after the class name. The constructor is declared using the "constructor" keyword and does not require optional parameters. For example,
The constructor keyword can be skipped if no access modifiers or annotations are provided. The primary constructor cannot contain any code, so the initialization code must be placed in a separate initializer block that is prefixed with the init keyword.class Sample constructor(val param1: Int, val param2: Int) { // code }
For example,fun main(args: Array<String>) { val s1 = Sample(1, 2) } class Sample(param1 : Int , param2: Int) { val p: Int var q: Int // initializer block init { p = param1 q = param2 println("The first parameter value is : $p") println("The second parameter value is : $q") } }
Output:-
When the object s1 is created for the class Sample, the constructor arguments a and b are given the values 1 and 2, respectively. There are two attributes listed for the classes p and q. When an object is created, the initializer block is invoked, which not only sets up the attributes but also prints them to standard output.The first parameter value is: 1 The second parameter value is: 2
-
Secondary Constructor - Secondary constructors work largely like primary constructors. However, in addition to the work done as a primary constructor, they can also allow for the initialization of variables and adding logic to the class. For example,
// KOTLIN fun main(args: Array<String>) { val s1 = Sample(1, 2) } class Sample { constructor(a: Int, b: Int) { println("The first parameter value is : $p") println("The second parameter value is : $q") } }
Output:-
The compiler chooses which secondary constructor will be invoked based on the supplied inputs. The compiler decides for us because we don't specify which constructor to use in the program above.The first parameter value is: 1 The second parameter value is: 2
A class in Kotlin can have up to one primary constructor and one or more secondary constructors. The primary constructor initializes the class, and the secondary constructor adds some additional logic.
The parentheses operator must first be used to define the parameters that the expression will accept. The lambda expression body can then be specified as an executable block of code enclosed in curly braces using the arrow operator:
val components = listOf(1, 2, 3, 4, 5)
components.fold(0, {
acc: Int, i: Int ->
print("acc = $acc, i = $i, ")
val result = acc + i
println("result = $result")
result
})
After the function name and any required arguments, enclose a lambda expression in parentheses to pass it as an argument to the function. This will result in the lambda expression's code being run each time the function is called.
Note: Commonly asked Kotlin interview questions.
An extension function is designated for a particular type and can be used with variables of that type. On the other hand, a regular function can be called on any variable because it is not designed for a particular variable.
The main distinction between a regular member function and an extension function is that the former employs runtime dispatch (i.e., dispatch based on the object's runtime type). In contrast, the latter employs compile-time dispatch (based on compile-time type information). An extension function is much more dependable and gives the code more latitude. For instance, you can dispatch based on generic type (type erasure prevents you from doing it with members).
Even if you ignore the style guide, you will soon begin to use extensions to replace all auxiliary methods. The only issue with extensions is how unsightly they appear in the Java code.
Note: Commonly asked Kotlin interview questions.
The process of automatically determining a variable's or expression's type from its value is known as type inference. When variables are first declared in Kotlin, type inference determines their type as well as the return type of functions.
When working with generic builders, the type inference (also known as the builder inference) is extremely helpful. By storing type information about other calls inside a builder call's lambda argument, it enables the compiler to infer the type arguments of that call. Consider the following example:
fun addEntryToMap(baseMap: Map<String, Number>, additionalEntry: Pair<String, Int>?) {
val myMap = buildMap {
putAll(baseMap)
if (additionalEntry != null) {
put(additionalEntry.first, additionalEntry.second)
}
}
}
Although builder inference can examine the calls inside the lambda argument, there is not enough type information present to infer type arguments in a conventional manner. The compiler can automatically determine the type arguments of the buildMap() call into String and Number based on the type information about the putAll() and put() calls. When using generic builders, you can omit type arguments thanks to builder inference.
In Kotlin, there are primarily two methods for sorting lists.
The first is by using the sort method, which sequentially orders the list using natural ordering.
The second option is the sortedBy function, which immediately sorts the list and outputs a fresh sorted version.
val numbers = listOf("one", "two", "three", "four")
println("Sorted ascending: ${numbers.sorted()}")
println("Sorted descending: ${numbers.sortedDescending()}")
Using the code below, explain what you understand by Lazy initialization.
val test: String by lazy {
val testString = "some value"
}
The lazy initialization feature of Kotlin allows you to specify that a variable won't be initialized until you use it in your code. Then, only one initialization will take place. After that, you can then that value.
When implementing a lazy property, the lazy() function is used, which accepts a lambda and returns an instance of lazy. During the first call to get(), the lambda function gets executed and passed to lazy(), which then remembers the result; subsequent calls to get() simply return the remembered result.
Note: Commonly asked Kotlin interview questions.
Yes, there are many cases where you can use both IntArray and Array<Int> interchangeably. However, this could lead you to believe that they are truly interchangeable. This is definitely not the case; using them interchangeably is considered bad practice.
The IntArray is an int[] type array, whereas Array<Int> in an Interger[] type. For most cases, they will work the same but will be treated differently during calls to functions such as Integer.valueOf(), where you will receive a type mismatch error.
There are some differences between the fold and reduce in Kotlin:
Fold: The fold calls the lambda you pass on its first execution along with an initial value. It will be given that initial value as well as the collection's first element as parameters.
listOf(1, 2, 3).fold(0) { sum, element -> sum + element }
The initial call to the lambda will be with parameters 0 and 1. If you need to provide a default value or parameter for your operation, the ability to pass in an initial value is helpful.
Reduce: There is no initial value required by the "reduce." Instead, it uses the collection's first element as the accumulator at the beginning.
listOf(1, 2, 3).reduce { sum, element -> sum + element }
The example above is denoted by sum. Here the first call to the lambda will be with parameters 1 and 2.
Note: Commonly asked Kotlin interview questions.
Here are the main usages of @JVMField, @JvmOverloads, and @JvmStatic:
- @JvmStatic: The @JvmStatic annotation tells the Java compiler that this annotation method is a static method and can be utilized in Java code.
- @JvmOverloads: The @JvmOverloads annotation is necessary when using the default values passed as arguments in Kotlin code from Java code.
- @JvmField: Without using getters and setters, Java code can access the fields of a Kotlin class using the @JvmField annotation. In the Kotlin code, we must use the @JvmField.
Kotlin lacks the async and await keywords, and these words aren't even included in its standard library, unlike many other programming languages with comparable features.
kotlinx.coroutines, created by JetBrains, is a comprehensive library for coroutines in Kotlin. Launch, async, and other high-level coroutine-capable primitives are present in this library. In addition, Kotlin provides us with an API that helps us write our asynchronous code sequentially.
Coroutines are described in the Kotlin documentation as being similar to thin threads. However, they are lightweight because they don't allocate new threads when they are created. Instead, they make use of intelligent scheduling and predefined thread pools.
Scheduling is the process of choosing the tasks to be completed in a particular order, and it determines which task you will carry out next. Furthermore, the execution of the Coroutines can be paused and resumed. This implies that a lengthy task could be completed one step at a time. Moreover, coroutines can be easily stopped and started as often as necessary.
Note: Commonly asked Kotlin interview questions.
The class property cannot be declared inside the secondary constructor. This will result in an error because we declare a class property's id in the secondary constructor, which is against the rules.
If you want to use some property in the secondary constructor, then you must define the property class and use it in the secondary constructor:
class Employee (var name: String) {
var id: Int = -1
init() {
println("Employee has got a name as $name")
}
constructor(secname: String, id: Int) this(secname) {
this.id = id
}
}
To refactor the above code using apply, you can write:
class Message(message: String, signature: String) {
val body = MessageBody().apply {
text = message + "\n" + signature
}
}
A function used as an argument in another function is known as a lambda expression. A function that lacks a name and cannot be used as an argument by another function is known as an anonymous function. They are actually opposites of one another. Some other differences are:
- While a lambda expression is transformed into a private method, an anonymous class object generates a separate class file after compilation that increases the size of a jar file. To bind this method dynamically, it uses an invokedynamic bytecode instruction, which saves both time and memory.
- This keyword is used to denote the current class in lambda expressions, whereas it can also denote a specific anonymous class in the case of an anonymous class.
- A lambda expression is only used for functional interfaces, while anonymous classes can be used when there are multiple abstract methods.
- Lambda expressions only require the function body, whereas redundant class definitions are required when dealing with anonymous classes.
The code for inline functions is copied and pasted into the body of the code where they are called to expand them inline at the call site. This can increase performance by removing the need for a function call and making the code easier to read.
However, it is impossible to declare a virtual or local function as inline. Here are some statements and expressions that are not supported anywhere inside the inline functions:
- Declaration of local classes
- Declaration of inner nested classes
- Function expressions
- Declarations of local function
- Default value for optional parameters
In the code given, we are creating a sealed class named "Sample," and within it, two sub-classes named "A" and "B." We are creating an instance of both sub-classes and calling their print method in the main function. Running the code above yields the following result:
Instance of the subclass B of sealed class Example
Instance of the subclass A of sealed class Example
Sealed classes are a significant new class form not found in Java, introduced by Kotlin. As the name suggests, sealed classes follow bounded or constrained class hierarchies. A class with a number of subclasses is said to be sealed. It is used when it is anticipated that a type will adhere to one of the subclass types. Type safety is ensured through sealed classes, which restrict the types that can be matched at compile time rather than at runtime.
Reified types are accessible at runtime as well as during compilation. Reified types in Kotlin provide runtime metadata about a type, such as its name or the names of its members. They are also frequently employed in generic programming and reflection. However, it is a sophisticated feature that beginners to the Kotlin language do not frequently use.
Reifying the generic type information at runtime means that users no longer need to pass type information as an argument. All we have to do is to mark the type parameter with the reified keyword:
inline fun <reified T> ObjectMapper.readValue(data: ByteArray): T =
readValue(data, object : TypeReference<T>() {})
Since Kotlin will inline the function and is already aware of the call site context, this is possible.
Classes and their members are final in Kotlin, making it difficult to use frameworks and libraries like Spring AOP, which demand that classes be open. By making classes and their members open without the explicit “open” keyword, the all-open compiler plugin modifies Kotlin to meet the needs of those frameworks.
For instance, only classes with specific annotations like @Configuration or @Service must be open when using Spring. Such annotations can be specified with All-open.
Let's move on to creating our first plugin.
First, we have to add the plugin artifact to help build script dependencies and then apply the plugin:
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
}
}
apply plugin: "kotlin-allopen"
We can also you can enable it using the plugins block:
plugins {
id "org.jetbrains.kotlin.plugin.allopen" version "1.7.10"
}
Next, specify the list of annotations that will make classes open:
allOpen {
annotation("com.my.Annotation")
// annotations("com.another.Annotation", "com.third.Annotation")
}
Note: If we annotate a class with com.my.Annotation, then the class and all its members will become open.
Moreover, it also works with meta-annotations:
@com.my.Annotation
annotation class MyFrameworkAnnotation
@MyFrameworkAnnotation
class MyClass // will be all-open
MyFrameworkAnnotation is annotated with the all-open meta-annotation com.my.Annotation, so it becomes an all-open annotation as well.
Note: Commonly asked Kotlin interview questions.
Tailored Scorecard to Avoid Wrong Engineering Hires
Handcrafted Over 6 years of Hiring Tech Talent