5 Basic Kotlin Interview Questions with Answers
1. What is Kotlin and what are its key advantages over Java?
Answer: Kotlin is a statically typed programming language developed by JetBrains that runs on the Java
Virtual Machine (JVM). It's 100% interoperable with Java and has been Google's preferred language for
Android development since 2019.
Key Advantages:
Conciseness:
kotlin
// Java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
// Kotlin
data class Person(var name: String, var age: Int)
Null Safety:
kotlin
var name: String = "John" // Non-nullable
var nullable: String? = null // Nullable
println(name.length) // Safe
println(nullable?.length) // Safe call operator
Other advantages:
Type Inference: Automatically determines variable types
Extension Functions: Add functionality to existing classes
Coroutines: Built-in support for asynchronous programming
Smart Casting: Automatic type casting after null checks
Default Parameters: Reduce method overloading
2. Explain the difference between val and var in Kotlin. When would you use
each?
Answer: val and var are used to declare variables in Kotlin, but they have different mutability
characteristics.
val (Value - Immutable Reference):
kotlin
val name = "John" // Type inferred as String
val age: Int = 25 // Explicit type declaration
// name = "Jane" // Compilation error - cannot reassign
val list = mutableListOf(1, 2, 3)
list.add(4) // OK - the list content can change
// list = mutableListOf() // Error - cannot reassign the reference
var (Variable - Mutable Reference):
kotlin
var count = 0 // Can be reassigned
count = 10 // OK
count++ // OK
var message: String? = null
message = "Hello" // OK - can reassign
When to use each:
Use val when:
The reference should not change after initialization
You want immutability (preferred for functional programming)
Declaring constants or configuration values
Following the principle of "prefer immutability"
Use var when:
The value needs to change during execution
Working with counters, accumulators, or state variables
The variable represents mutable state
Best Practice:
kotlin
// Prefer val by default
val users = getUsersFromDatabase()
val currentUser = users.find { it.id == userId }
// Use var only when necessary
var attempts = 0
while (attempts < maxAttempts && !isSuccessful()) {
attempts++
tryOperation()
}
3. What are nullable types in Kotlin and how do you handle null safely?
Answer: Kotlin's type system distinguishes between nullable and non-nullable types to eliminate null
pointer exceptions at compile time.
Nullable vs Non-nullable Types:
kotlin
var name: String = "John" // Non-nullable - cannot be null
var nickname: String? = null // Nullable - can be null
// name = null // Compilation error
nickname = null // OK
Safe Null Handling Techniques:
1. Safe Call Operator ( ?. ):
kotlin
val length = nickname?.length // Returns null if nickname is null
println(nickname?.uppercase()) // Only calls if not null
2. Elvis Operator ( ?: ):
kotlin
val displayName = nickname ?: "Unknown" // Use default if null
val length = nickname?.length ?: 0 // Chain with safe call
3. Safe Cast Operator ( as? ):
kotlin
val stringValue = someObject as? String // Returns null if cast fails
4. Not-null Assertion ( !! ):
kotlin
val length = nickname!!.length // Throws exception if null - use carefully!
5. let function for null checks:
kotlin
nickname?.let { name ->
println("Nickname is: $name")
// This block only executes if nickname is not null
}
6. Traditional null checks:
kotlin
if (nickname != null) {
println(nickname.length) // Smart cast - treated as non-null
}
Best Practices:
kotlin
// Good: Safe handling
fun processUser(user: User?) {
val email = user?.email ?: return
val domain = email.substringAfter("@").takeIf { it.isNotEmpty() } ?: "unknown"
println("User domain: $domain")
}
// Avoid: Overusing not-null assertion
fun badExample(user: User?) {
println(user!!.name) // Dangerous - can crash
}
4. Explain Kotlin data classes and their benefits. How do they differ from
regular classes?
Answer: Data classes in Kotlin are classes designed to hold data. They automatically generate useful
methods and provide concise syntax for creating classes that primarily store state.
Basic Data Class:
kotlin
data class User(
val id: Int,
val name: String,
val email: String,
var isActive: Boolean = true // Default parameter
)
Auto-generated Methods:
kotlin
val user1 = User(1, "John", "john@email.com")
val user2 = User(1, "John", "john@email.com")
// toString() - automatically generated
println(user1) // User(id=1, name=John, email=john@email.com, isActive=true)
// equals() and hashCode() - based on primary constructor parameters
println(user1 == user2) // true (structural equality)
println(user1 === user2) // false (referential equality)
// copy() - create copies with some properties changed
val inactiveUser = user1.copy(isActive = false)
val renamedUser = user1.copy(name = "Jane")
// componentN() functions for destructuring
val (id, name, email) = user1
println("User $name has ID $id")
Comparison with Regular Classes:
Regular Class:
kotlin
class RegularUser(val id: Int, val name: String, val email: String) {
// Must manually implement if needed
override fun toString(): String {
return "RegularUser(id=$id, name=$name, email=$email)"
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is RegularUser) return false
return id == other.id && name == other.name && email == other.email
}
override fun hashCode(): String {
return Objects.hash(id, name, email)
}
// No copy() method available
// No destructuring support
}
Data Class Requirements:
Must have at least one parameter in primary constructor
All primary constructor parameters must be val or var
Cannot be abstract, open, sealed, or inner
Can implement interfaces and extend classes (with restrictions)
Benefits:
1. Less Boilerplate: Automatic generation of common methods
2. Consistency: Standard implementations of equals/hashCode
3. Immutability Support: Encourages use of val properties
4. Copy Functionality: Easy object copying with modifications
5. Destructuring: Convenient property extraction
6. Readable toString(): Automatic string representation
Advanced Usage:
kotlin
data class Product(
val id: String,
val name: String,
val price: Double,
val category: String
) {
// Additional methods can still be added
fun discountedPrice(discountPercent: Double): Double {
return price * (1 - discountPercent / 100)
}
// Properties not in constructor don't participate in equals/hashCode
val displayName: String get() = name.uppercase()
}
// Usage
val products = listOf(
Product("1", "Laptop", 999.99, "Electronics"),
Product("2", "Mouse", 29.99, "Electronics")
)
// Destructuring in loops
for ((id, name, price) in products) {
println("$name costs $price")
}
5. What are Kotlin extension functions and how do you create them? Provide
examples.
Answer: Extension functions allow you to add new functionality to existing classes without modifying
their source code or using inheritance. They appear to be part of the class but are actually resolved
statically.
Basic Extension Function Syntax:
kotlin
// Extend String class with a function to count words
fun String.wordCount(): Int {
return this.split("\\s+".toRegex()).size
}
// Usage
val text = "Hello world from Kotlin"
println(text.wordCount()) // Output: 4
Extension Function Components:
kotlin
fun ReceiverType.functionName(parameters): ReturnType {
// 'this' refers to the receiver object
// Function body
}
Examples:
1. Extending Built-in Classes:
kotlin
// Extension for Int
fun Int.isEven(): Boolean = this % 2 == 0
// Extension for List
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null
// Usage
println(42.isEven()) // true
println(listOf(1, 2, 3).secondOrNull()) // 2
println(listOf(1).secondOrNull()) // null
2. Extending Custom Classes:
kotlin
data class Person(val firstName: String, val lastName: String, val age: Int)
// Extension function
fun Person.fullName(): String = "$firstName $lastName"
// Extension property
val Person.isAdult: Boolean
get() = age >= 18
// Usage
val person = Person("John", "Doe", 25)
println(person.fullName()) // John Doe
println(person.isAdult) // true
3. Generic Extension Functions:
kotlin
// Extension for any nullable type
fun <T> T?.isNotNull(): Boolean = this != null
// Extension for collections
fun <T> Collection<T>.isNotNullOrEmpty(): Boolean = !this.isNullOrEmpty()
// Usage
val name: String? = null
println(name.isNotNull()) // false
val list = listOf(1, 2, 3)
println(list.isNotNullOrEmpty()) // true
4. Practical Examples:
kotlin
// Date formatting extension
fun Long.toDateString(): String {
val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return formatter.format(Date(this))
}
// String validation extensions
fun String.isValidEmail(): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
}
fun String.isValidPhoneNumber(): Boolean {
return this.matches(Regex("^\\+?[1-9]\\d{1,14}$"))
}
// Usage
val timestamp = System.currentTimeMillis()
println(timestamp.toDateString()) // 2024-01-15
val email = "test@example.com"
println(email.isValidEmail()) // true
Important Characteristics:
1. Static Resolution:
kotlin
open class Shape
class Rectangle : Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
val shape: Shape = Rectangle()
println(shape.getName()) // "Shape" - resolved based on compile-time type
2. Member Functions Take Precedence:
kotlin
class Example {
fun existing() = "Member function"
}
fun Example.existing() = "Extension function" // Will be ignored
val example = Example()
println(example.existing()) // "Member function"
3. Extension Properties:
kotlin
// Extension property (must have getter)
val String.lastChar: Char
get() = this[length - 1]
var StringBuilder.lastChar: Char
get() = this[length - 1]
set(value) { this.setCharAt(length - 1, value) }
// Usage
println("Hello".lastChar) // 'o'
Best Practices:
Use extensions to add utility functions to existing types
Keep extensions simple and focused
Consider creating extension files organized by functionality
Use meaningful names that clearly indicate the extension's purpose
Be careful with nullable receiver types