Questions for Kotlin Basics & Syntax
1. What is the difference between `val` and `var` in Kotlin?
In Kotlin, `val` is used to declare a read-only (immutable) variable,
whereas `var` is used for mutable variables that can be reassigned.
val shrek: String = "Cannot be reassigned at a later point"
var donkey: String = "Can be reassigned"
shrek = "Green Ogre" // Not Allowed
donkey = "talkative & friendly" // Allowed
Trying to change the value of `val` will give an error in the IDE (Intellij or similar)
2. Explain data classes in Kotlin.
Data classes in Kotlin are classes that are primarily used to hold data.
They automatically provide implementations for methods like `toString()`, `equals()`, `hashCode()`, and `copy()` based on the declared properties.
data class Shrek(
val name: String = "Shrek",
val species: String = "Ogre",
val age: Int,
val personalityTraits: List,
val friends: List,
val spouse: String? = null,
val residence: String = "Swamp"
)
Sample Usage:
val shrek = Shrek(
age = 30,
personalityTraits = listOf("Grumpy", "Loyal", "Caring", "Sarcastic"),
friends = listOf("Donkey", "Fiona", "Puss in Boots"),
spouse = "Fiona"
)
3. What is a sealed class in Kotlin?
A sealed class in Kotlin is used to represent restricted class hierarchies,
where a value can have one of the predefined types. It helps in exhaustive `when` expressions.
Example usage as a UI state holder:
sealed class UiState {
object Loading : UiState()
data class Success(val data: T) : UiState()
data class Error(val message: String, val retry: (() -> Unit)? = null) : UiState()
}
Example: Representing User Actions or Events
sealed class UserAction {
object Refresh : UserAction()
data class Search(val query: String) : UserAction()
data class SelectItem(val itemId: Int) : UserAction()
object LoadMore : UserAction()
}
Example: Representing User Actions or Events
sealed class FormValidationState {
object Valid : FormValidationState()
data class Invalid(val errors: List) : FormValidationState()
object Empty : FormValidationState()
}
4. How does Kotlin handle null safety?
Kotlin has built-in null safety features. Variables cannot hold null values unless explicitly declared as nullable using `?`.
Kotlin also provides safe calls (`?.`) and the Elvis operator (`?:`) to handle null values.
Example: Null Safety with Elvis Operator
fun handleNullWithElvis(input: String?) {
val value = input ?: "Default Value" // Use `value` here safely, knowing it's not null.
println(value) // Prints the value or "Default Value" if input is null.
}
Example: Null Safety with Safe Call Operator
fun handleNullWithSafeCall(input: String?) {
val upperCaseValue = input?.uppercase() // Converts the input to uppercase only if it's not null.
println(upperCaseValue ?: "Input was null") // Prints the uppercase string or "Input was null" if input is null.
}
5. What are extension functions in Kotlin?
Kotlin provides a powerful and expressive feature called extension functions.
With extension functions, you can add new functionality to existing classes without modifying their source code or creating a subclass.
This is particularly useful when working with third-party libraries or built-in classes where you cannot modify the original implementation.
Extension functions allow you to write cleaner, more concise, and more readable code by extending the capabilities of a class.
They are defined as regular functions, prefixed by the class they extend, and can be invoked just like a normal method on an instance of that class.
Example: Adding an Extension Function to String
fun String.capitalizeWords(): String {
return this.split(" ").joinToString(" ") { it.capitalize() } // Capitalizes each word in the string.
}
Usage
fun main() {
val text = "hello kotlin extension functions"
val result = text.capitalizeWords() // Calls the extension function directly on a String.
println(result) // Output: "Hello Kotlin Extension Functions"
}
6. What is the purpose of `lateinit` in Kotlin?
The `lateinit` keyword in Kotlin is used to declare a non-null variable that will be initialized later in the program.
Unlike regular variables, which must be initialized at the time of declaration, `lateinit` allows you to defer initialization.
It is particularly useful in scenarios like dependency injection, Android view bindings,
or other cases where the variable cannot be initialized in the constructor or at declaration.
- The variable must be mutable (`var`), not immutable (`val`).
- Accessing the variable before initialization will throw an `UninitializedPropertyAccessException`.
Example:
lateinit var name: String // Declare a variable to be initialized later
fun initializeName() {
name = "Kotlin"
println(name) // Output: Kotlin
}
7. What is the difference between `==` and `===` in Kotlin?
In Kotlin, `==` and `===` serve different purposes:
- `==`: Checks structural equality, meaning it compares the content or value of two objects.
It is equivalent to the `equals()` method in Java.
- `===`: Checks referential equality, meaning it checks whether two references point to the exact same object in memory.
- Use `==` when you care about the content of the objects.
- Use `===` when you care about the identity of the objects.
Example:
val a = "hello"
val b = "hello"
val c = a
println(a == b) // true (Structural equality: both have the same content)
println(a === b) // false (Referential equality: different objects in memory)
println(a === c) // true (Both references point to the same object)
8. Explain the `apply` function in Kotlin.
The `apply` function is one of Kotlin's scope functions.
It is used to configure or initialize an object and returns the same object.
This function is particularly useful for setting up an object instance in a clean and concise way.
Key Characteristics:
- The object is available inside the `apply` block as `this`.
- The function always returns the original object, making it chainable with other calls.
Use Cases:
- Setting properties of an object during initialization.
- Configuring complex objects without creating temporary variables.
Example:
data class Person(var name: String = "", var age: Int = 0)
fun main() {
val person = Person().apply {
name = "John"
age = 30
}
println(person.name) // Output: John
println(person.age) // Output: 30
}
9. What are coroutines in Kotlin?
Coroutines are a feature in Kotlin designed to simplify asynchronous programming.
They enable writing non-blocking, concurrent code in a sequential and readable manner.
Unlike traditional threading, coroutines are lightweight and managed by the Kotlin runtime.
Key Characteristics:
- They can suspend execution at certain points without blocking threads.
- They allow performing long-running tasks (like network or I/O operations) efficiently.
- They are managed via structured concurrency provided by the `CoroutineScope`.
Common Functions:
- `launch`: Starts a coroutine but does not return a result.
- `async`: Starts a coroutine and returns a `Deferred` object, representing a future result.
Example:
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("Coroutine starts")
delay(1000L) // Simulate a long-running task
println("Coroutine ends")
}
println("Main program continues")
}
10. What is the difference between `launch` and `async` in Kotlin coroutines?
Both `launch` and `async` are functions used to create coroutines in Kotlin, but they serve different purposes:
- `launch`: Creates a coroutine that does not return a value. It is used for jobs that do not produce a result.
- `async`: Creates a coroutine that returns a `Deferred` object, which represents a future result. Use this when you need to perform a task and retrieve its result later.
- `launch` is used for fire-and-forget tasks.
- `async` is used for concurrent tasks that return a value.
Example:
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("Launch coroutine starts")
delay(1000L)
println("Launch coroutine ends")
}
val result = async {
println("Async coroutine starts")
delay(1000L)
"Async result" // Returns a value
}
println("Async result: ${result.await()}")
}
11. How does Kotlin differ from Java in terms of exception handling?
Kotlin does not have checked exceptions, which means the compiler does not enforce the handling of exceptions using `try-catch` blocks.
This design choice simplifies code by removing the need to declare or catch exceptions explicitly, but it also requires developers to be
more cautious and proactive about error handling.
Key Differences:
- Java enforces the use of `try-catch` or `throws` declarations for checked exceptions.
- Kotlin treats all exceptions as unchecked, giving developers more flexibility and reducing boilerplate code.
Example:
fun divide(a: Int, b: Int): Int {
return a / b // May throw an ArithmeticException if b is zero
}
fun main() {
try {
println(divide(10, 0))
} catch (e: ArithmeticException) {
println("Exception caught: ${e.message}")
}
}
12. What is an inline function in Kotlin?
An inline function in Kotlin is a function where the compiler replaces the function call with the actual code of the function at runtime.
This optimization reduces the overhead associated with function calls, especially when using higher-order functions with lambdas.
Key Characteristics:
- Avoids creating additional objects for lambda expressions.
- Improves performance by inlining the code directly where the function is called.
- Particularly useful for higher-order functions.
Example:
inline fun measureTime(block: () -> Unit) {
val start = System.currentTimeMillis()
block() // The lambda code will be inlined here
val end = System.currentTimeMillis()
println("Execution took ${end - start}ms")
}
fun main() {
measureTime {
for (i in 1..1000) {
println(i)
}
}
}
13. What is the use of the `companion object` in Kotlin?
The `companion object` in Kotlin is an object associated with a class that allows defining members that are shared across all instances
of the class. It provides a way to declare static-like members or factory methods.
Key Characteristics:
- Acts as a singleton tied to the class.
- Can implement interfaces and have properties and methods.
- Provides a clean alternative to static methods in Java.
Example:
class MyClass {
companion object {
const val CONSTANT = "Shared Constant"
fun printMessage() = println("This is a companion object function")
}
}
fun main() {
println(MyClass.CONSTANT) // Accessing a constant in the companion object
MyClass.printMessage() // Calling a function in the companion object
}
14. What is the purpose of the `by` keyword in Kotlin?
The `by` keyword in Kotlin is used for delegation. It simplifies the implementation of interfaces or property getters and setters by
delegating their functionality to another object.
Key Use Cases:
- Property delegation (e.g., lazy initialization).
- Delegating interface implementation to another object.
Example - Property Delegation:
class LazyExample {
val value: String by lazy {
println("Computed!")
"Hello, Kotlin!"
}
}
fun main() {
val example = LazyExample()
println(example.value) // First access computes the value
println(example.value) // Subsequent accesses use the cached value
}
Example - Interface Delegation:
interface Printer {
fun print()
}
class RealPrinter : Printer {
override fun print() = println("Printing from RealPrinter")
}
class DelegatedPrinter(printer: Printer) : Printer by printer
fun main() {
val printer = RealPrinter()
val delegatedPrinter = DelegatedPrinter(printer)
delegatedPrinter.print() // Delegates call to RealPrinter
}
15. What is the difference between `object` and `class` in Kotlin?
In Kotlin, `class` and `object` are used to define types, but they differ in instantiation and usage:
Key Differences:
- `class`: Defines a blueprint for objects and requires explicit instantiation using the `new` keyword.
- `object`: Creates a singleton instance, which is instantiated immediately when accessed.
- `object` can also be used for declaring anonymous objects.
Example - `class`:
class Person(val name: String)
fun main() {
val person = Person("John") // Explicit instantiation
println(person.name)
}
Example - `object`:
object Singleton {
val name = "Singleton Instance"
}
fun main() {
println(Singleton.name) // Access singleton properties
}
16. Explain higher-order functions in Kotlin.
Higher-order functions are functions that take other functions as parameters or return functions as results. They enable functional
programming patterns in Kotlin.
Key Features:
- Promote code reusability and abstraction.
- Allow passing behavior as arguments.
Example:
fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
fun main() {
val sum = operateOnNumbers(5, 10) { x, y -> x + y }
println(sum) // Output: 15
}