1. What is Kotlin?
Difficulty: EasyType: MCQTopic: Kotlin Basics
- A modern, statically-typed programming language that runs on JVM
- A JavaScript framework
- A database management system
- An operating system
Kotlin is a modern, statically-typed programming language developed by JetBrains that runs on the Java Virtual Machine and can also be compiled to JavaScript or native code. It is fully interoperable with Java, meaning you can use Java libraries and frameworks in Kotlin and vice versa. Kotlin was officially announced as a preferred language for Android development by Google in 2019, and it offers features like null safety, extension functions, coroutines, and concise syntax that make development more productive and safer.
Correct Answer: A modern, statically-typed programming language that runs on JVM
2. What is the difference between val and var in Kotlin?
Difficulty: EasyType: MCQTopic: Variables
- val is immutable (read-only) and var is mutable
- val is mutable and var is immutable
- Both are the same
- val is for integers and var is for strings
In Kotlin, val declares a read-only variable that can be assigned only once, similar to final in Java, promoting immutability and safer code. var declares a mutable variable that can be reassigned multiple times. Using val is preferred whenever possible as it makes code more predictable and thread-safe by preventing accidental modifications. The Kotlin compiler encourages using val by showing warnings when var could be replaced with val, helping developers write more functional and immutable code.
Correct Answer: val is immutable (read-only) and var is mutable
3. What does the ? operator mean in Kotlin type declarations?
Difficulty: MediumType: MCQTopic: Null Safety
- It makes the type nullable, allowing null values
- It makes the type non-nullable
- It's a ternary operator
- It's used for type casting
The question mark operator in type declarations like String? makes the type nullable, meaning the variable can hold either a value of that type or null. By default, Kotlin types are non-nullable, which eliminates most NullPointerException errors at compile time. To work with nullable types, you use safe call operators like question mark dot for safe access, elvis operator question mark colon for default values, or not-null assertion operator double exclamation for forcing non-null. This null safety system is one of Kotlin's most powerful features preventing null reference errors.
Correct Answer: It makes the type nullable, allowing null values
4. How do you insert a variable into a string in Kotlin?
Difficulty: EasyType: MCQTopic: Strings
- Using $ symbol like "Hello $name"
- Using + operator like "Hello " + name
- Using % symbol like "Hello %name"
- Using # symbol like "Hello #name"
Kotlin uses string templates with the dollar sign to embed variables and expressions directly in strings, making string concatenation more readable and concise than using plus operators. You can insert simple variables with dollar name or evaluate expressions with dollar curly braces like dollar curly braces name dot length curly braces. This feature is similar to template literals in JavaScript or f-strings in Python, and it works in both single-line and multi-line strings, making string manipulation in Kotlin very convenient and expressive.
Correct Answer: Using $ symbol like "Hello $name"
5. What is type inference in Kotlin?
Difficulty: EasyType: MCQTopic: Type System
- The compiler automatically determines the type of a variable from its initializer
- Manually specifying types for all variables
- Converting one type to another
- Checking types at runtime
Kotlin has powerful type inference that automatically determines variable types from their initialization values, allowing you to omit explicit type declarations in most cases while maintaining static type safety. For example, val name equals John automatically infers String type without writing val name colon String. This reduces boilerplate while keeping type safety, and the compiler can infer types for variables, function return types, and generic type parameters. However, you can still explicitly declare types for clarity or when the inferred type is not what you want.
Correct Answer: The compiler automatically determines the type of a variable from its initializer
6. What does the ?: (Elvis operator) do in Kotlin?
Difficulty: MediumType: MCQTopic: Null Safety
- Returns the left value if not null, otherwise returns the right value
- Checks if two values are equal
- Performs division
- Creates a nullable type
The Elvis operator question mark colon provides a concise way to handle null values by returning the left operand if it's not null, or the right operand if the left is null. It's commonly used with safe call operators like val length equals name question mark dot length question mark colon 0 which returns the string length or 0 if name is null. This operator is named Elvis because the question mark colon looks like Elvis Presley's hairstyle when viewed sideways. It's more readable than traditional if-null checks and helps write null-safe code concisely.
Correct Answer: Returns the left value if not null, otherwise returns the right value
7. What happens when you use the ?. (safe call) operator on a null object?
Difficulty: MediumType: MCQTopic: Null Safety
- It returns null instead of throwing NullPointerException
- It throws NullPointerException
- It returns 0
- It returns an empty string
The safe call operator question mark dot safely accesses properties or calls methods on nullable objects, returning null if the object is null instead of throwing a NullPointerException. For example, name question mark dot length returns the length if name is not null, or null if name is null. This operator can be chained like person question mark dot address question mark dot city to safely navigate nested nullable properties. It's one of Kotlin's key null safety features that eliminates the need for verbose null checks while preventing null pointer exceptions.
Correct Answer: It returns null instead of throwing NullPointerException
8. What does the !! (not-null assertion) operator do and when should you use it?
Difficulty: HardType: MCQTopic: Null Safety
- Converts nullable type to non-nullable, throws NPE if null, use sparingly when certain value is not null
- Makes a type nullable
- Checks if value is null safely
- Always safe to use for null handling
The not-null assertion operator double exclamation converts a nullable type to non-nullable, explicitly telling the compiler you guarantee the value is not null, and it will throw a KotlinNullPointerException if the value is actually null. Use it sparingly only when you're absolutely certain the value cannot be null but the compiler cannot infer that, such as after null checks in complex scenarios or when working with Java code that doesn't have null annotations. Overusing double exclamation defeats Kotlin's null safety and is considered a code smell, so prefer safe call or Elvis operators when possible, and use let, also, or require for better alternatives.
Correct Answer: Converts nullable type to non-nullable, throws NPE if null, use sparingly when certain value is not null
9. What are the main advantages of Kotlin over Java?
Difficulty: EasyType: SubjectiveTopic: Kotlin vs Java
Kotlin offers null safety by default with nullable and non-nullable types eliminating most NullPointerExceptions at compile time, much more concise syntax with features like data classes, type inference, and extension functions reducing boilerplate significantly. It provides modern features like coroutines for asynchronous programming making concurrent code simpler and more readable, smart casts that automatically cast types after checks, default and named parameters eliminating constructor overloading, and sealed classes for representing restricted class hierarchies. Kotlin is fully interoperable with Java allowing gradual migration and use of Java libraries, includes functional programming features with higher-order functions and lambdas, has better collection APIs with operations like map filter and reduce built-in, and offers extension functions to add functionality to existing classes without inheritance. The language is more expressive and safer while maintaining Java's performance and ecosystem access.
10. Explain the difference between val and var with examples and when to use each.
Difficulty: EasyType: SubjectiveTopic: Variables
Use val for read-only variables that are assigned once and never change, like val pi equals 3.14 or val name equals John, which is similar to final in Java and promotes immutability making code safer and more predictable. Use var for mutable variables that need to be reassigned, like var counter equals 0 followed by counter plus plus, necessary for loops, accumulators, or state that legitimately changes. Prefer val as the default choice and only use var when you genuinely need to mutate the variable, as immutability prevents accidental changes, makes code easier to reason about especially in concurrent environments, and aligns with functional programming principles. The Kotlin style guide recommends using val whenever possible, and the IDE even suggests converting var to val when it detects the variable is never reassigned, helping enforce this best practice.
11. Explain Kotlin's null safety system and the different operators for handling nulls.
Difficulty: MediumType: SubjectiveTopic: Null Safety
Kotlin's null safety system distinguishes nullable types with question mark like String question mark from non-nullable types like String, making null checks part of the type system and preventing NullPointerExceptions at compile time. Use safe call question mark dot to safely access nullable properties returning null if the object is null, Elvis operator question mark colon to provide default values for null cases, let function with safe call to execute code only when non-null, and not-null assertion double exclamation only when absolutely certain value is not null as it throws exception if null. The system also includes safe casts with as question mark that return null instead of throwing ClassCastException, requireNotNull function to throw IllegalArgumentException with custom message if value is null useful for validation, and checkNotNull for similar checks. This comprehensive null safety eliminates billion-dollar mistake of null references while keeping code concise and readable.
12. How do you work with strings in Kotlin including templates, multi-line strings, and common operations?
Difficulty: MediumType: SubjectiveTopic: Strings
Kotlin strings support templates with dollar sign for variables and dollar curly braces for expressions like val message equals Hello dollar name your age is dollar curly braces age plus 1 curly braces making string building more readable than concatenation. Use triple quotes for multi-line strings preserving formatting and line breaks useful for SQL queries or JSON templates, with trimIndent or trimMargin to clean up indentation. Strings in Kotlin are immutable like Java and have rich APIs including substring, split, replace, trim, startsWith, endsWith, contains, and more. You can also use raw strings for regular expressions without escaping backslashes, convert strings to numbers with toInt or toIntOrNull for safe conversion, and use string comparison with equals or double equals checking value equality not reference. Extension functions let you add custom string operations easily, and Kotlin's string handling is more convenient than Java while maintaining compatibility.
13. Explain type casting in Kotlin including smart casts, safe casts, and unsafe casts.
Difficulty: HardType: SubjectiveTopic: Type System
Kotlin provides smart casts that automatically cast types after is checks eliminating explicit casts, so after if x is String you can use x dot length directly without casting. Use safe cast operator as question mark that returns null if cast fails rather than throwing ClassCastException, useful when you're unsure if object is the target type like val str equals obj as question mark String. Unsafe cast operator as throws ClassCastException if cast fails, use only when certain of the type like val str equals obj as String. Smart casts work in if expressions, when expressions, and while loops, but require the variable to be immutable val or local var to ensure type safety. The compiler tracks type information through control flow, smart casting in the appropriate branches, and you can use is for type checks, not is for negative checks, and combine with when expressions for elegant type-based branching. Understanding these casting mechanisms helps write type-safe code that's both concise and robust.
14. What are the basic data types in Kotlin and how do they differ from Java primitives?
Difficulty: EasyType: SubjectiveTopic: Data Types
Kotlin has basic types like Int, Long, Short, Byte, Double, Float for numbers, Boolean for true false values, Char for single characters, and String for text, which appear as objects in code but are optimized to primitives by the compiler when possible for performance. Unlike Java which distinguishes primitive types like int from wrapper objects like Integer, Kotlin treats everything as objects providing a consistent API while the compiler handles primitive optimization automatically. All numeric types support standard operations, and you can use underscores in numeric literals for readability like val million equals 1_000_000. Kotlin doesn't have implicit numeric conversions requiring explicit conversion like toInt, toLong, or toDouble for type conversion, preventing subtle bugs from automatic widening. Types can be nullable with question mark suffix, and Kotlin provides better type inference reducing the need to specify types explicitly while maintaining static type safety.
15. Explain operator overloading in Kotlin and how it differs from Java.
Difficulty: MediumType: SubjectiveTopic: Operators
Kotlin supports operator overloading allowing you to define how operators like plus, minus, times work with your custom types by defining functions with specific names like operator fun plus, making code more intuitive and readable. Unlike Java which doesn't support operator overloading, Kotlin lets you write expressions like point1 plus point2 instead of point1 dot add point2, but this must be done responsibly to maintain code clarity. Standard operators have corresponding function names like plus for plus, minus for minus, times for asterisk, div for slash, rem for percent, and you mark these functions with operator keyword. You can overload comparison operators equals, compareTo, index access operators get set for square brackets, invoke operator for calling objects as functions, and even in operator for contains checks. Kotlin also supports operator overloading for extension functions letting you add operator support to existing classes, but use this feature judiciously as overloading operators poorly can make code confusing rather than clearer.
16. How does Kotlin achieve interoperability with Java and what challenges might you face?
Difficulty: HardType: SubjectiveTopic: Java Interop
Kotlin is designed for seamless Java interoperability allowing you to call Java code from Kotlin and vice versa without any wrappers or special syntax, use Java libraries and frameworks directly, and mix Java and Kotlin code in the same project. Kotlin understands Java types, automatically converting platform types from Java that may or may not be null to appropriate Kotlin nullable or non-nullable types based on annotations like Nullable or NotNull. Challenges include Java's null handling where platform types require careful handling since Java doesn't enforce null safety, SAM conversions work automatically for Java interfaces but may need explicit syntax for Kotlin interfaces, and getters setters conventions where Java getter setters are accessed as properties in Kotlin but you must use methods when calling Kotlin from Java. Kotlin generates JVM bytecode compatible with Java, uses JvmName and JvmStatic annotations to control generated names and static methods, and handles generics, varargs, and checked exceptions differently requiring awareness when crossing language boundaries. Understanding these interoperability details helps write code that works smoothly in mixed codebases and when using Java libraries.
17. What is the correct syntax to declare a function in Kotlin?
Difficulty: EasyType: MCQTopic: Functions
- fun functionName(params): ReturnType { }
- function functionName(params) ReturnType { }
- def functionName(params): ReturnType { }
- void functionName(params) { }
Kotlin uses the fun keyword to declare functions, followed by the function name, parameters in parentheses, colon and return type, and the function body in curly braces. The return type can be omitted if the function returns Unit (equivalent to void in Java), and for single-expression functions you can use equals sign syntax without curly braces.
Kotlin's function syntax is more concise than Java's, and the return type comes after the function signature unlike Java where it comes before. This syntax makes functions first-class citizens in Kotlin and enables features like higher-order functions and lambdas.
Correct Answer: fun functionName(params): ReturnType { }
18. How do you define default parameter values in Kotlin functions?
Difficulty: EasyType: MCQTopic: Functions
- Assign values directly in parameter list like fun greet(name: String = "Guest")
- Use @Default annotation
- Define in function body
- Kotlin doesn't support default parameters
Kotlin allows you to specify default values for function parameters directly in the parameter list using the equals sign, eliminating the need for multiple overloaded versions of the same function. When calling a function, you can omit arguments that have default values, and they will use the specified defaults.
This feature dramatically reduces boilerplate compared to Java where you need to create multiple constructor or method overloads to achieve the same functionality. Default parameters work with named arguments for even more flexibility in function calls.
Correct Answer: Assign values directly in parameter list like fun greet(name: String = "Guest")
19. What are extension functions in Kotlin?
Difficulty: MediumType: MCQTopic: Extensions
- Functions that add new functionality to existing classes without inheritance
- Functions that extend the execution time
- Functions that work only with extended classes
- Functions with extended parameter lists
Extension functions allow you to add new functions to existing classes without modifying their source code or using inheritance, defined with the class name followed by dot before the function name. You can extend any class including final classes or classes from libraries, and the function can be called as if it were a member of that class.
Extension functions are resolved statically at compile time based on the declared type, not the runtime type, and they cannot access private members of the class they extend. This feature is one of Kotlin's most powerful capabilities for writing expressive and readable code while keeping classes clean.
Correct Answer: Functions that add new functionality to existing classes without inheritance
20. What is a higher-order function in Kotlin?
Difficulty: MediumType: MCQTopic: Higher Order
- A function that takes functions as parameters or returns a function
- A function with more parameters
- A function that calls itself recursively
- A function defined at the top of the file
Higher-order functions are functions that take other functions as parameters or return functions as results, enabling functional programming patterns in Kotlin. They allow you to write more abstract and reusable code by passing behavior as parameters, commonly used with collection operations like map, filter, and fold.
Kotlin's lambda syntax makes higher-order functions easy to use and read, with features like trailing lambda syntax when the last parameter is a function. This pattern is fundamental to Kotlin's functional programming capabilities and enables writing elegant, declarative code.
Correct Answer: A function that takes functions as parameters or returns a function
21. What is the syntax for a lambda expression in Kotlin?
Difficulty: EasyType: MCQTopic: Lambdas
- { parameters -> body } enclosed in curly braces
- (parameters) => body with arrow function syntax
- lambda(parameters) { body } with lambda keyword
- function(parameters) { body } with function keyword
Kotlin lambda expressions are enclosed in curly braces with parameters before the arrow and the body after, like val sum equals open curly x comma y arrow x plus y close curly. If the lambda has a single parameter, you can use the implicit it variable instead of declaring the parameter name.
Lambdas can capture variables from their surrounding scope, and if a lambda is the last parameter of a function call, it can be placed outside the parentheses for better readability. This syntax is concise and integrates seamlessly with Kotlin's higher-order functions.
Correct Answer: { parameters -> body } enclosed in curly braces
22. How does the when expression differ from switch in Java?
Difficulty: MediumType: MCQTopic: Control Flow
- when is an expression returning values, more powerful, and doesn't need break statements
- when and switch are exactly the same
- when only works with integers
- when requires break statements
The when expression is Kotlin's replacement for switch statements but is much more powerful, allowing any type of condition including ranges, type checks, and arbitrary expressions, and it returns a value making it a true expression. Unlike Java's switch which falls through cases by default requiring break statements, when automatically breaks after each branch preventing common bugs.
You can use when with or without an argument, match multiple values with commas, use in for range checks, use is for type checks, and even use arbitrary boolean expressions. When must be exhaustive if used as an expression, meaning it must cover all possible cases or include an else branch.
Correct Answer: when is an expression returning values, more powerful, and doesn't need break statements
23. How do you create a range in Kotlin?
Difficulty: EasyType: MCQTopic: Control Flow
- Use .. operator like 1..10 for inclusive range
- Use range(1, 10) function
- Use [] operator like [1:10]
- Use - operator like 1-10
Kotlin uses the dot dot operator to create ranges like 1 dot dot 10 which includes both endpoints, or use until for exclusive end like 1 until 10 excluding 10. You can use step to specify increment like 1 dot dot 10 step 2 for odd numbers, or downTo for descending ranges like 10 downTo 1.
Ranges work with in operator for containment checks, with for loops for iteration, and with when expressions for matching. Character ranges like a dot dot z and custom ranges for your own types are also supported, making ranges a versatile feature for iteration and bounds checking.
Correct Answer: Use .. operator like 1..10 for inclusive range
24. What is the benefit of named arguments in Kotlin function calls?
Difficulty: MediumType: MCQTopic: Functions
- Makes code more readable and allows skipping default parameters
- Makes code run faster
- Required for all function calls
- Only works with lambdas
Named arguments let you specify parameter names when calling functions like greet open paren name equals John comma age equals 25 close paren, making code self-documenting and improving readability especially for functions with many parameters. They allow you to skip parameters with default values without specifying all preceding parameters, and you can provide arguments in any order.
Named arguments are particularly useful when working with functions that have multiple boolean or similar-typed parameters where the meaning might be unclear from position alone. They combine well with default parameters to create flexible and readable function APIs.
Correct Answer: Makes code more readable and allows skipping default parameters
25. Explain function types in Kotlin and how to use them as parameters and return values.
Difficulty: MediumType: SubjectiveTopic: Function Types
Function types in Kotlin are declared using parentheses for parameters and arrow for return type like open paren Int comma Int close paren arrow Int for a function taking two Ints and returning Int, and you can store functions in variables, pass them as parameters, or return them from functions. You can make function types nullable with question mark, provide parameter names for clarity like open paren x colon Int comma y colon Int close paren arrow Int, and use typealias to create readable names for complex function types.
Higher-order functions use function types as parameters enabling functional programming patterns, and you can invoke function type variables by calling them like regular functions. Extension function types like String dot open paren close paren arrow Int define functions that extend a type, useful for DSL creation and APIs that configure objects.
Function types are fundamental to Kotlin's functional programming support, enabling patterns like callbacks, strategies, decorators, and functional composition. Understanding function types helps you write more flexible and reusable code using functions as first-class values.
26. How do you create extension functions and what are their limitations and best practices?
Difficulty: HardType: SubjectiveTopic: Extensions
Create extension functions by prefixing the function name with the receiver type followed by a dot like fun String dot reverse open paren close paren colon String equals this dot reversed open paren close paren, where this refers to the receiver object. Extension functions are resolved statically based on the declared type not the runtime type, meaning they don't support polymorphism, and they cannot access private or protected members of the class they extend only public members.
Use extension functions to add utility methods to classes you don't own, create domain-specific APIs without modifying original classes, and organize related utility functions near the types they work with rather than in separate util classes. Be careful not to pollute the namespace with too many extensions, and prefer member functions when you need access to private state or virtual dispatch.
Best practices include keeping extensions focused and cohesive, organizing them in files grouped by the types they extend, documenting extensions well since they appear like member functions but have different access rules, and considering making extensions private or internal to limit their scope. Extension functions are powerful but should be used thoughtfully to maintain code clarity.
27. Explain lambda syntax in Kotlin including it parameter, trailing lambdas, and capturing variables.
Difficulty: MediumType: SubjectiveTopic: Lambdas
Lambda syntax in Kotlin uses curly braces with optional parameters before arrow and body after like open curly x comma y arrow x plus y close curly, but if there's only one parameter you can omit it and use the implicit it variable. Lambdas can capture variables from their surrounding scope making them closures, and captured variables can be modified inside the lambda unlike Java's effectively final requirement.
Trailing lambda syntax allows moving the last lambda parameter outside parentheses for better readability like list dot filter open paren close paren open curly it greater 5 close curly, and if the lambda is the only argument you can omit the parentheses entirely. This makes DSL-like code and builder patterns very readable in Kotlin.
Understand that lambdas are compiled to function objects or inlined depending on context, and using inline functions can eliminate lambda overhead for performance-critical code. Lambda receivers like String dot open paren close paren arrow Unit enable DSL creation where this refers to the receiver type inside the lambda body.
28. How do you use when expressions in Kotlin for different types of conditions and pattern matching?
Difficulty: MediumType: SubjectiveTopic: Control Flow
Use when with an argument to match against specific values like when open paren x close paren open curly 1 arrow print first, 2 comma 3 arrow print second or third, in 4 dot dot 10 arrow print in range close curly, supporting multiple values with commas, ranges with in, type checks with is, and arbitrary expressions. When without an argument works like if-else chains allowing any boolean conditions like when open curly x greater 0 arrow print positive, x less 0 arrow print negative, else arrow print zero close curly.
When is an expression returning the value of the matched branch, so you can assign it to variables or return it from functions, but then it must be exhaustive covering all cases or including else. Use when for type-based dispatching with is checks getting automatic smart casts in the branches, sealed class hierarchies for compile-time exhaustiveness checking, and complex conditional logic that would be ugly with nested if-else.
When is more powerful and flexible than Java's switch, supporting rich pattern matching and making code more declarative and readable. It's one of Kotlin's most versatile control flow constructs combining the benefits of switch, if-else, and pattern matching.
29. What are inline functions in Kotlin and when should you use them?
Difficulty: HardType: SubjectiveTopic: Inline Functions
Inline functions are marked with the inline keyword causing the compiler to copy the function body to the call site instead of creating a function call, eliminating the overhead of lambda objects and function calls which is especially important for higher-order functions. When you inline a function that takes lambdas as parameters, the lambda code is also inlined avoiding lambda object creation and virtual calls.
Use inline for small higher-order functions that are called frequently to avoid performance overhead, enable non-local returns from lambdas allowing return from the enclosing function, and use noinline parameter modifier when you need to store a lambda parameter or call it from a nested function. Use crossinline when you need to prevent non-local returns but still want inlining benefits.
Don't overuse inline as it increases code size since the function body is duplicated at each call site, and avoid inlining large functions or functions with complex control flow. Inline is most beneficial for collection operations and other higher-order functions used in hot code paths where performance matters.
30. Explain the different loop constructs in Kotlin including for, while, and repeat.
Difficulty: EasyType: SubjectiveTopic: Control Flow
Kotlin's for loop iterates over anything that provides an iterator using in keyword like for open paren item in collection close paren, works with ranges like for open paren i in 1 dot dot 10 close paren, and supports destructuring like for open paren index comma value close paren in list dot withIndex open paren close paren. There's no traditional C-style for loop with initialization, condition, and increment in Kotlin since ranges and functional approaches cover those use cases better.
While and do-while loops work like Java for condition-based iteration like while open paren condition close paren and do open curly body close curly while open paren condition close paren for executing at least once. The repeat function provides a simple way to execute code multiple times like repeat open paren 10 close paren open curly body close curly avoiding the need for a counter variable.
Kotlin's loop constructs are more expressive than Java's, and in many cases functional operations like forEach, map, or filter are preferred over explicit loops for better readability and immutability. Break and continue work in loops with optional labels for nested loop control.
31. Explain Unit, Nothing, and single-expression functions in Kotlin.
Difficulty: MediumType: SubjectiveTopic: Functions
Unit is Kotlin's type for functions that don't return a meaningful value equivalent to void in Java, but Unit is an actual type with a singleton value allowing it to be used in generic contexts. You can omit Unit return type declaration as it's the default, and functions returning Unit can use return without a value or simply let the function end.
Nothing is a special type representing a value that never exists, used for functions that never return normally like functions that always throw exceptions or enter infinite loops. Nothing is a subtype of all types making it useful in type-safe error handling and control flow analysis where the compiler knows execution doesn't continue.
Single-expression functions can use equals syntax instead of curly braces like fun double open paren x colon Int close paren equals x times 2, automatically inferring the return type from the expression. This syntax is concise for simple functions and clearly indicates the function is pure with no side effects, commonly used for property getters, simple transformations, and lambda-like utility functions.
32. What is tail recursion in Kotlin and how does the tailrec modifier optimize recursive functions?
Difficulty: HardType: SubjectiveTopic: Recursion
Tail recursion occurs when a recursive call is the last operation in a function with no further processing after it returns, and Kotlin optimizes tail-recursive functions marked with tailrec modifier by converting them into iterative loops avoiding stack overflow. The compiler transforms tail-recursive functions into loops reusing the same stack frame rather than creating new frames for each recursive call, enabling safe recursive implementations of algorithms that would otherwise overflow the stack.
To use tailrec, ensure the recursive call is truly the last operation with no further computation after it, not followed by any operations like addition or method calls. The function must call itself directly not through another function or lambda, and the compiler warns you if tailrec cannot be applied helping you identify non-tail-recursive patterns.
Use tail recursion for algorithms naturally expressed recursively like factorial, fibonacci, or list processing where iteration would be less clear, but be aware that not all recursive algorithms can be expressed in tail-recursive form. Understanding tail recursion helps write efficient recursive code without stack limitations while maintaining functional programming style.
33. How do you declare a basic class in Kotlin?
Difficulty: EasyType: MCQTopic: Classes
- class ClassName { } or class ClassName for empty class
- public class ClassName { }
- def class ClassName { }
- type ClassName = class { }
Kotlin classes are declared using the class keyword followed by the class name, and if the class has no body you can omit the curly braces. Classes are public and final by default unlike Java where they are package-private, and you need to explicitly mark classes as open to allow inheritance.
The primary constructor can be declared in the class header like class Person open paren val name colon String close paren making property declaration and initialization very concise. Kotlin's class syntax reduces boilerplate significantly compared to Java while maintaining full object-oriented capabilities.
Correct Answer: class ClassName { } or class ClassName for empty class
34. What is a primary constructor in Kotlin?
Difficulty: EasyType: MCQTopic: Constructors
- A constructor declared in the class header with optional init block
- The first constructor defined in the class body
- A constructor marked with @Primary annotation
- A constructor that must be called first
The primary constructor is declared in the class header like class Person open paren val name colon String comma var age colon Int close paren, and parameters can be declared as properties using val or var. You can add initialization code in init blocks that run when the instance is created, in the order they appear in the class body.
The primary constructor cannot contain any code, so init blocks are used for initialization logic like validation or logging. Primary constructors with property declarations make simple data-holding classes extremely concise, eliminating Java's boilerplate of field declarations, getters, setters, and constructor assignments.
Correct Answer: A constructor declared in the class header with optional init block
35. What methods does the Kotlin compiler automatically generate for data classes?
Difficulty: MediumType: MCQTopic: Data Classes
- equals(), hashCode(), toString(), copy(), and componentN() functions
- Only getters and setters
- Only toString()
- Only equals() and hashCode()
Data classes marked with data keyword automatically generate equals open paren close paren and hashCode open paren close paren for structural equality, toString open paren close paren for readable string representation, copy open paren close paren for creating modified copies, and componentN open paren close paren functions for destructuring. These generated methods are based on properties declared in the primary constructor.
Data classes must have at least one parameter in the primary constructor, and parameters should be val or var to be included in the generated methods. Data classes cannot be abstract, open, sealed, or inner, and they provide all the boilerplate you'd typically write for POJOs or value objects in Java automatically.
Correct Answer: equals(), hashCode(), toString(), copy(), and componentN() functions
36. What is the purpose of sealed classes in Kotlin?
Difficulty: HardType: MCQTopic: Sealed Classes
- Restrict class hierarchies to a limited set of types known at compile time
- Make classes immutable
- Prevent class instantiation
- Make classes thread-safe
Sealed classes define restricted class hierarchies where all subclasses must be declared in the same file or as nested classes, enabling the compiler to know all possible subclasses at compile time. This is extremely useful for representing finite state machines, result types with success and error cases, or UI states where you want exhaustive when expressions.
The compiler can verify exhaustiveness of when expressions over sealed class hierarchies, warning you when you haven't handled all cases without needing an else branch. Sealed classes are abstract by default and their constructors are private, combining benefits of enums with the flexibility of allowing subclasses to hold different data and have multiple instances.
Correct Answer: Restrict class hierarchies to a limited set of types known at compile time
37. What is an object declaration in Kotlin?
Difficulty: MediumType: MCQTopic: Objects
- A singleton implementation where the object keyword creates a single instance
- A way to declare multiple instances
- A function that returns objects
- A type declaration similar to typedef
Object declarations use the object keyword to create singletons lazily initialized when first accessed, ensuring only one instance exists throughout the application. You can access members directly through the object name without calling constructors, and objects can implement interfaces and extend classes like regular classes.
Object declarations are thread-safe by default with initialization guaranteed to happen only once even in concurrent access scenarios. They're perfect for implementing singletons, creating companion objects, or defining anonymous objects for one-off implementations without the boilerplate of singleton patterns in Java.
Correct Answer: A singleton implementation where the object keyword creates a single instance
38. What is a companion object in Kotlin?
Difficulty: MediumType: MCQTopic: Companion Objects
- An object inside a class whose members can be called using the class name
- A paired class that always exists together
- A helper class for companions
- An object that implements interfaces
Companion objects are declared inside a class with the companion object keywords, and their members can be accessed using the class name like ClassName dot memberName similar to Java static members. A class can have only one companion object, which can implement interfaces, extend classes, and have its own properties and functions.
Companion objects are useful for factory methods, constants related to the class, or methods that don't need instance state. Unlike Java static members, companion objects are actual objects that can inherit from classes and implement interfaces, making them more flexible for polymorphism and dependency injection.
Correct Answer: An object inside a class whose members can be called using the class name
39. What is the default visibility modifier in Kotlin?
Difficulty: EasyType: MCQTopic: Access Modifiers
- public - visible everywhere
- private - visible only in the same file
- protected - visible to subclasses
- internal - visible in the same module
Kotlin's default visibility is public unlike Java's package-private, meaning declarations are visible everywhere by default. Kotlin provides private for visibility within the same file or class, protected for visibility in subclasses, and internal for visibility within the same module which is a compilation unit like a Gradle project or Maven artifact.
The internal modifier is unique to Kotlin providing better encapsulation than Java's package-private which can be circumvented by creating classes in the same package. Kotlin also doesn't have package-private visibility, encouraging proper encapsulation through well-defined module boundaries rather than package-level access control.
Correct Answer: public - visible everywhere
40. How do you make a class inheritable in Kotlin?
Difficulty: MediumType: MCQTopic: OOP Basics
- Mark the class with the open keyword
- Use the extends keyword
- All classes are inheritable by default
- Use @Inheritable annotation
Kotlin classes are final by default to encourage composition over inheritance and prevent fragile base class problems, so you must explicitly mark classes with open keyword to allow inheritance. Methods and properties also need to be marked open to be overridable, making the class's contract explicit about what can be extended.
To inherit from a class, use colon syntax like class Derived colon Base open paren close paren, calling the base class constructor. This design encourages safer inheritance by making extensibility intentional rather than accidental, following the principle that classes should be designed for inheritance or prohibited from it.
Correct Answer: Mark the class with the open keyword
41. Explain how properties work in Kotlin including getters, setters, and backing fields.
Difficulty: MediumType: SubjectiveTopic: Properties
Kotlin properties declared with val or var automatically generate getter and setter methods, accessed using simple property syntax like person dot name instead of person dot getName open paren close paren. You can customize getters and setters using get open paren close paren equals and set open paren value close paren blocks after the property declaration, and val properties only have getters while var properties have both.
Backing fields are accessed using the field identifier inside custom accessors, and Kotlin automatically creates a backing field only if needed based on whether you use field in the accessor or define a completely custom implementation. Custom getters and setters allow you to add validation, lazy initialization, or computed properties while maintaining property syntax for callers.
Kotlin properties are more powerful than Java fields, combining field declarations with accessor methods in a clean syntax that reduces boilerplate while providing full control when needed. Properties can also be delegated using by keyword for patterns like lazy initialization or observable properties without writing repetitive accessor code.
42. What are data classes and when should you use them? What are their requirements?
Difficulty: EasyType: SubjectiveTopic: Data Classes
Data classes marked with data keyword are specialized classes designed to hold data with automatically generated equals, hashCode, toString, copy, and destructuring component functions based on primary constructor properties. Use data classes for DTOs, API models, database entities, or any class primarily holding immutable or mutable data without significant behavior or business logic.
Data classes must have a primary constructor with at least one parameter marked as val or var, and they cannot be abstract, open, sealed, or inner. All the generated methods only consider properties declared in the primary constructor, so properties declared in the class body won't be included in equality checks or other generated methods.
The copy function is particularly useful for creating modified copies of immutable objects using named parameters like val updated equals original dot copy open paren age equals 30 close paren. Data classes eliminate dozens of lines of boilerplate compared to Java POJOs while ensuring correct implementations of equals, hashCode, and toString that respect all properties.
43. How do sealed classes work with when expressions and why are they useful for representing state?
Difficulty: HardType: SubjectiveTopic: Sealed Classes
Sealed classes restrict inheritance to a known set of subclasses defined in the same file, enabling exhaustive when expressions where the compiler verifies you've handled all possible cases without needing an else branch. When you use a sealed class in when, the compiler knows all possible subclasses and can check exhaustiveness at compile time, catching missing cases and providing better type safety than open hierarchies or enums.
Sealed classes are perfect for representing finite state machines, result types like Success or Error, or UI states in Android where you want type-safe state handling. Unlike enums which can only have single instances and limited data, sealed class subclasses can have different properties, multiple instances, and can be data classes or objects as needed.
Use sealed classes for discriminated unions, API response modeling where you have success with data and error with message, navigation destinations in Android, or any scenario with a closed set of possibilities. The combination of sealed classes and when expressions provides pattern matching-like capabilities with compile-time safety ensuring all cases are handled.
44. Explain companion objects, how they differ from Java static members, and common use cases.
Difficulty: HardType: SubjectiveTopic: Companion Objects
Companion objects declared with companion object inside a class provide a namespace for members that logically belong to the class rather than instances, accessed via the class name like ClassName dot memberName. Unlike Java static members which are not objects, companion objects are actual singleton objects that can implement interfaces, be passed as arguments, and participate in inheritance making them more flexible for polymorphism and dependency injection.
Common use cases include factory methods that create class instances with validation or custom initialization logic, constants and utility functions related to the class, and implementing interfaces at the class level for features like Parcelable in Android. You can give companion objects explicit names like companion object Factory, or implement interfaces like companion object colon JsonDeserializer for type-safe factories.
Companion objects can have extension functions allowing you to add functionality to classes even from outside their definition, useful for separation of concerns or adding Android-specific extensions. While they serve similar purposes to Java static members, companion objects being real objects enables patterns impossible with static members like dependency injection of companion object implementations or testing with mock companions.
45. Explain primary and secondary constructors, init blocks, and the order of initialization in Kotlin.
Difficulty: MediumType: SubjectiveTopic: Constructors
Primary constructors are declared in the class header and can have parameters marked as properties with val or var, while secondary constructors are defined in the class body with constructor keyword and must delegate to the primary constructor using this. Init blocks run during object construction in the order they appear in the class, after the primary constructor parameters are initialized, allowing you to add validation or initialization logic that can't be expressed in property initializers.
The initialization order is primary constructor parameters, property initializers and init blocks in the order they appear, then secondary constructor body. Properties declared with val or var in the primary constructor are available throughout the class including in init blocks and property initializers, but you cannot access properties before they're initialized leading to compile errors if order is wrong.
Use primary constructors for simple initialization with property declarations, init blocks for validation or initialization logic that needs multiple statements, and secondary constructors for providing alternative construction paths like default parameters or convenience constructors. Most classes only need a primary constructor possibly with default parameters, making secondary constructors less common in Kotlin than constructor overloading in Java.
46. How do interfaces work in Kotlin and how do they differ from abstract classes?
Difficulty: MediumType: SubjectiveTopic: OOP Basics
Kotlin interfaces can contain abstract method declarations, default implementations using method bodies, and property declarations which must be abstract or have custom accessors, declared with interface keyword. Unlike Java 8 interfaces, Kotlin interfaces can have properties but cannot store state meaning no backing fields, and classes implement interfaces using colon syntax like class MyClass colon MyInterface.
Interfaces differ from abstract classes in that a class can implement multiple interfaces but inherit from only one abstract class, interfaces cannot have constructors or stored properties with backing fields, and abstract classes can have state and initialization blocks. Both can have abstract and concrete methods, but interfaces are preferred when defining contracts without implementation or when you need multiple inheritance of type.
Use interfaces for defining capabilities or contracts like Clickable, Serializable, or Repository where implementations might vary widely, and abstract classes for modeling is-a relationships with shared code and state. Kotlin's interface properties and default implementations make interfaces more powerful than Java interfaces while maintaining the multiple inheritance benefits that abstract classes cannot provide.
47. What are object expressions (anonymous objects) and when should you use them?
Difficulty: HardType: SubjectiveTopic: Objects
Object expressions create anonymous objects on the fly using object colon Type syntax, useful for implementing interfaces, extending classes, or creating objects without declaring a named class. They can access and modify variables from their enclosing scope unlike Java anonymous classes which have restrictions, making them more powerful for closures and callbacks.
Use object expressions for one-off implementations like event listeners in Android, temporary adapters or comparators, or when you need to override methods in a single location without creating a separate class. Object expressions can implement multiple interfaces, extend a single class, and add extra properties or methods not in the implemented interfaces or superclass.
The key difference from object declarations is that object expressions create new instances each time they're evaluated, while object declarations are singletons. For type safety, if an object expression is used as a local or private declaration its type is the anonymous type, but if returned from public functions or assigned to public properties, its type is the declared supertype making additional members inaccessible.
48. How does Kotlin support the delegation pattern with the by keyword for class and property delegation?
Difficulty: HardType: SubjectiveTopic: Delegation
Kotlin provides first-class support for the delegation pattern using the by keyword, allowing a class to delegate implementation of an interface to another object like class Derived open paren val base colon Base close paren colon Base by base where all interface methods are automatically forwarded to the base object. This eliminates boilerplate delegation code where you'd manually forward each method call, and you can still override specific methods if needed while delegating the rest.
Property delegation using by allows delegating getter and setter implementation to another object following the getValue and setValue operator conventions, enabling patterns like lazy initialization with lazy, observable properties with Delegates dot observable, storing properties in a map with map delegation, or custom delegates. Built-in delegates include lazy for thread-safe lazy initialization computed only on first access, observable for property observers notified on changes, and vetoable for validation before allowing changes.
Class delegation is useful for the decorator pattern or when you want to expose an interface but delegate implementation to a contained object, while property delegation reduces boilerplate for common patterns like lazy initialization or validation. Understanding delegation helps you write more maintainable code by separating concerns and reusing delegation logic across properties or classes.
49. Explain the difference between nested classes and inner classes in Kotlin.
Difficulty: MediumType: SubjectiveTopic: Nested Classes
Nested classes in Kotlin are declared inside another class but are static by default, meaning they don't hold a reference to the outer class instance and cannot access outer class members directly. They're useful for grouping related classes together without creating unnecessary coupling, like defining a Builder class inside the class it builds.
Inner classes marked with the inner keyword do hold a reference to the outer class instance accessed with this at OuterClass syntax, allowing them to access outer class members including private ones. Inner classes are necessary when the nested class needs to interact with the outer class state, common in callback implementations or adapter patterns.
This default behavior is opposite to Java where nested classes are non-static by default potentially causing memory leaks if the nested class outlives the outer class, while Kotlin's static-by-default nested classes prevent accidental memory leaks. Only use inner when you genuinely need access to the outer class, otherwise prefer nested classes for better encapsulation and avoiding unintended references.
50. What are the main collection interfaces in Kotlin?
Difficulty: EasyType: MCQTopic: Collections
- List, Set, and Map - each with mutable and immutable versions
- Only ArrayList, HashSet, and HashMap
- Array, Vector, and Dictionary
- Collection, Container, and Storage
Kotlin provides three main collection interfaces: List for ordered collections allowing duplicates, Set for unordered collections with unique elements, and Map for key-value pairs. Each interface has both read-only versions like List, Set, Map and mutable versions like MutableList, MutableSet, MutableMap.
Read-only collections don't have methods to add or remove elements, promoting immutability and safer code, though the underlying implementation might still be mutable. Mutable collections provide methods like add, remove, and clear for modifying the collection.
This separation between read-only and mutable interfaces is a key Kotlin feature not present in Java, helping prevent accidental modifications and making code intent clearer.
Correct Answer: List, Set, and Map - each with mutable and immutable versions
51. How do you create an immutable list in Kotlin?
Difficulty: EasyType: MCQTopic: Collections
- Use listOf() function
- Use ArrayList()
- Use new List()
- Use mutableListOf()
The listOf function creates a read-only List where you cannot add or remove elements, like val numbers equals listOf open paren 1 comma 2 comma 3 close paren. For mutable lists that can be modified, use mutableListOf which returns a MutableList.
Kotlin's list creation functions are more concise than Java's verbose ArrayList instantiation and initialization. You can also use emptyList for empty lists or listOfNotNull to filter out null values automatically.
Read-only doesn't mean truly immutable as the underlying implementation might be mutable, but the interface prevents modifications through that reference, providing compile-time safety against accidental changes.
Correct Answer: Use listOf() function
52. What does the map function do on collections?
Difficulty: EasyType: MCQTopic: Collection Ops
- Transforms each element and returns a new collection with the results
- Filters elements based on a condition
- Reduces collection to a single value
- Sorts the collection
The map function applies a transformation to each element in a collection and returns a new collection with the transformed elements, like listOf open paren 1 comma 2 comma 3 close paren dot map open curly it times 2 close curly returning listOf open paren 2 comma 4 comma 6 close paren. It's one of the most commonly used functional operations for transforming data.
Map is a pure function that doesn't modify the original collection but creates a new one, promoting immutability and functional programming style. You can chain map with other operations for powerful data transformations.
Related functions include mapNotNull to filter out nulls, mapIndexed to access element indices, and flatMap to transform and flatten nested collections in one operation.
Correct Answer: Transforms each element and returns a new collection with the results
53. What does the filter function return?
Difficulty: EasyType: MCQTopic: Collection Ops
- A new collection containing only elements matching the predicate
- The first matching element
- A boolean indicating if any element matches
- The count of matching elements
The filter function returns a new collection containing only the elements that satisfy the given predicate condition, like numbers dot filter open curly it greater 5 close curly returns elements greater than 5. It doesn't modify the original collection but creates a new filtered version.
Filter is essential for functional-style data processing, often combined with map and other operations to build complex data transformation pipelines. Related functions include filterNot for inverse filtering and filterNotNull to remove null elements.
You can use filterIsInstance to filter by type with automatic casting, filterIndexed to access indices in the predicate, and partition to split a collection into two lists based on a condition.
Correct Answer: A new collection containing only elements matching the predicate
54. What is the main advantage of using Sequences over Collections for chained operations?
Difficulty: MediumType: MCQTopic: Sequences
- Lazy evaluation - operations are only performed when results are needed
- Faster for all operations
- Can store more elements
- Automatically parallel processing
Sequences use lazy evaluation where operations like map and filter are not executed until a terminal operation like toList or count is called, avoiding intermediate collection creation. This can significantly improve performance for large datasets or when you don't need all results.
With collections, each operation creates a new collection immediately, so chaining multiple operations creates multiple intermediate collections. Sequences process elements one by one through the entire chain, evaluating only what's necessary.
Use asSequence to convert collections to sequences, then chain operations, and finally call a terminal operation. Sequences are ideal for large data or infinite streams but have overhead for small collections where eager evaluation is faster.
Correct Answer: Lazy evaluation - operations are only performed when results are needed
55. What is the difference between let and apply scope functions?
Difficulty: HardType: MCQTopic: Scope Functions
- let passes object as 'it' and returns lambda result, apply uses 'this' and returns the object
- They are exactly the same
- let is for nullable objects only
- apply cannot access object properties
The let function passes the object as the lambda parameter accessible via it and returns the lambda's result, useful for transformations or null-safety with safe call operator. The apply function makes the object available as this inside the lambda and returns the object itself, ideal for object configuration.
Use let when you want to transform an object or execute code with non-null values like person question mark dot let open curly print it dot name close curly. Use apply for object initialization like Person open paren close paren dot apply open curly name equals John close curly.
Both are scope functions that help reduce temporary variables and make code more concise, but choosing between them depends on whether you need the lambda result or the original object returned.
Correct Answer: let passes object as 'it' and returns lambda result, apply uses 'this' and returns the object
56. What is the difference between reduce and fold operations?
Difficulty: MediumType: MCQTopic: Collection Ops
- fold requires an initial value, reduce uses first element as initial value
- reduce is faster than fold
- fold can only sum numbers
- They are exactly the same
The fold function takes an initial accumulator value and a lambda that combines the accumulator with each element, like numbers dot fold open paren 0 close paren open curly acc comma num arrow acc plus num close curly. The reduce function doesn't take an initial value but uses the first element as the initial accumulator.
Fold is safer for empty collections as it returns the initial value, while reduce throws an exception on empty collections. Use reduceOrNull for null-safe reduction on potentially empty collections.
Both are useful for aggregating collection elements into a single value like sums, products, or building strings, with fold being more flexible due to the explicit initial value allowing different result types than the element type.
Correct Answer: fold requires an initial value, reduce uses first element as initial value
57. Which function would you use to group collection elements by a key?
Difficulty: MediumType: MCQTopic: Collections
- groupBy - returns a Map of key to list of elements
- partition - splits into two lists
- associate - creates key-value pairs
- sortedBy - sorts by a key
The groupBy function groups elements by a key selector function returning a Map where keys are the results of the selector and values are lists of elements with that key, like people dot groupBy open curly it dot age close curly groups people by age. It's perfect for categorizing data based on properties.
Related functions include partition which splits a collection into two lists based on a predicate, associate which creates a Map from elements with custom key-value pairs, and groupingBy for more advanced grouping with aggregation operations.
GroupBy is commonly used for data analysis, creating lookup tables, or organizing data by categories, and it handles duplicate keys automatically by collecting all matching elements into lists.
Correct Answer: groupBy - returns a Map of key to list of elements
58. Explain the difference between map, flatMap, and flatten with examples of when to use each.
Difficulty: MediumType: SubjectiveTopic: Collections
The map function transforms each element to a new value creating a new collection of the same size, like transforming list of names to their lengths. Use map when you have a one-to-one transformation where each input element produces exactly one output element.
FlatMap transforms each element into a collection and then flattens the result into a single collection, useful when each element maps to multiple values like expanding each word into its characters. It's equivalent to calling map then flatten, combining transformation and flattening in one step.
The flatten function only flattens nested collections without transformation, taking a collection of collections and returning a single flat collection. Use it when you already have nested collections and just need to flatten them without any element transformation.
Example: map turns listOf 1 2 3 with times 2 into listOf 2 4 6, flatMap turns listOf abc def with chars into flat list of all characters, and flatten turns listOf listOf 1 2 listOf 3 4 into listOf 1 2 3 4.
59. Compare all five scope functions (let, run, with, apply, also) and explain when to use each.
Difficulty: HardType: SubjectiveTopic: Scope Functions
Let and also pass the object as it parameter, while run, with, and apply use this for context. Let and run return the lambda result allowing transformations, while apply and also return the original object enabling chaining. With is similar to run but not an extension function.
Use let for null-safe calls with question mark dot and transforming objects, apply for object configuration returning the object, run for complex initialization with result, also for side effects while chaining, and with for calling multiple methods on an object without extension syntax.
Examples: person question mark dot let prints name only if not null, Person dot apply sets properties, text dot also saves it while returning text, run combines initialization and returns result, with reads cleaner for multiple calls on one object.
Choosing the right scope function makes code more readable by clearly expressing intent whether you want the lambda result or original object, and whether the object should be it or this.
60. Explain when and why you should use Sequences instead of regular collections for better performance.
Difficulty: HardType: SubjectiveTopic: Sequences
Use sequences when chaining multiple operations on large collections because sequences use lazy evaluation processing elements one at a time through the entire chain, avoiding intermediate collection creation. Collections eagerly create a new collection for each operation, which is wasteful for large datasets or when you don't need all results.
Sequences are beneficial when you have multiple transformations like filter then map then take, as each element flows through all operations before the next element starts, enabling short-circuiting operations to stop early. For small collections under 100 elements, the overhead of lazy evaluation makes sequences slower than eager collections.
Convert to sequence with asSequence, chain operations, and use terminal operations like toList, first, or count to trigger evaluation. Sequences can represent infinite streams processed on-demand, and they excel when dealing with IO operations or expensive computations where avoiding unnecessary work matters.
Avoid sequences for simple single operations or small data where collection overhead is negligible, and always benchmark performance-critical code since intuition about performance can be wrong.
61. Explain the difference between mutable and immutable collections and when to use each.
Difficulty: MediumType: SubjectiveTopic: Collections
Immutable collections created with listOf, setOf, or mapOf don't have methods to add or remove elements, providing a read-only view that prevents modifications through that reference. Mutable collections created with mutableListOf, mutableSetOf, or mutableMapOf provide methods like add, remove, and clear for modifications.
Prefer immutable collections as the default choice because they're safer in concurrent code, prevent accidental modifications, make code easier to reason about, and clearly communicate that the collection won't change. Use mutable collections when you need to build collections incrementally or when performance requires in-place modifications.
Note that immutable doesn't mean the underlying implementation is unchangeable, just that the interface prevents modifications. For true immutability use defensive copying or immutable data structures libraries.
Good practice is to use mutable collections internally during construction, then expose them as immutable interfaces to callers using toList or returning List type instead of MutableList, protecting your internal state while allowing internal modifications.
62. How do you build efficient collection transformation pipelines in Kotlin?
Difficulty: MediumType: SubjectiveTopic: Collection Ops
Build transformation pipelines by chaining functional operations like filter, map, flatMap, and groupBy to process data declaratively. Each operation returns a new collection allowing method chaining, creating readable pipelines that clearly express data transformations step by step.
For performance with multiple operations on large collections, convert to sequence with asSequence to enable lazy evaluation, chain operations, then materialize results with terminal operations like toList or count. This avoids creating intermediate collections for each step.
Common patterns include filter to remove unwanted elements, map to transform, sortedBy to order, groupBy to categorize, and fold or reduce to aggregate. Use mapNotNull to transform and filter nulls in one step, and distinct to remove duplicates.
Keep pipelines readable by splitting very long chains across lines with each operation on its own line, and consider extracting complex transformations into named functions for reusability and testability.
63. Explain the associate, associateBy, and associateWith functions for creating Maps from collections.
Difficulty: HardType: SubjectiveTopic: Collection Ops
Associate creates a Map by transforming each element into a Pair of key and value using a lambda that returns key to value pairs, giving full control over both keys and values. AssociateBy uses elements as values and derives keys from a lambda, while associateWith uses elements as keys and derives values from a lambda.
Use associate when you need to transform elements into completely different keys and values, associateBy to create a lookup map where you keep original elements as values with custom keys, and associateWith to create maps where elements are keys with computed values.
Examples: people dot associateBy curly it dot id curly creates ID to person map, numbers dot associateWith curly it times it curly creates number to square map, and associate with key to value pairs for full transformation.
These functions handle duplicate keys by keeping the last occurrence, and you can use groupBy instead if you need to keep all elements with the same key. Understanding these associate functions helps avoid manual map building loops.
64. Explain partition, zip, and chunked functions and their use cases.
Difficulty: HardType: SubjectiveTopic: Collections
Partition splits a collection into two lists based on a predicate, returning a Pair where first contains elements matching the predicate and second contains non-matching elements. It's perfect for separating data into two categories like valid and invalid items in one pass.
Zip combines two collections into a list of Pairs, matching elements by position, useful for processing parallel arrays or creating coordinate pairs. If collections have different sizes, zip stops at the shortest length, and you can provide a custom transformation instead of creating Pairs.
Chunked breaks a collection into sublists of a specified size, with the last chunk potentially smaller, useful for batch processing or pagination. You can provide a transformation to process each chunk immediately, avoiding storing all chunks in memory.
Examples: numbers dot partition curly it greater 0 curly separates positive and negative, names dot zip ages creates name-age pairs, and list dot chunked 10 creates batches of 10 elements.
65. What are the performance characteristics of List, Set, and Map operations and how to choose the right collection?
Difficulty: HardType: SubjectiveTopic: Performance
Lists provide O of 1 random access by index and fast iteration but O of n contains checks and removals scanning the entire list. ArrayList is best for random access and iteration, LinkedList for frequent insertions and removals at specific positions though rarely needed in Kotlin.
Sets provide O of 1 contains, add, and remove operations using hash-based implementations like HashSet, making them ideal for uniqueness checking or fast membership tests. LinkedHashSet maintains insertion order with slightly more memory, TreeSet provides sorting with O of log n operations.
Maps provide O of 1 key-based lookup, insertion, and removal using HashMap, perfect for lookups and associations. LinkedHashMap maintains insertion order, TreeMap provides sorted keys with O of log n operations, use when key ordering matters.
Choose List for ordered data with index access, Set for unique elements with fast membership testing, and Map for key-value associations with fast lookups. Consider memory usage and typical operations to pick the right implementation.
66. What are Coroutines in Kotlin?
Difficulty: EasyType: MCQTopic: Coroutines
- Lightweight threads that allow writing asynchronous code in a sequential style
- Heavy system threads
- A database framework
- A UI framework
Coroutines are lightweight concurrency primitives in Kotlin that allow you to write asynchronous code that looks and behaves like sequential code, avoiding callback hell and making async programming much more readable. Unlike threads which are expensive system resources, you can launch thousands of coroutines without significant overhead.
Coroutines are suspended rather than blocked, meaning they don't occupy a thread while waiting for results, freeing the thread to do other work. This makes them ideal for IO operations, network calls, or any waiting that would normally block a thread.
Kotlin coroutines are built into the language with suspend functions and integrate seamlessly with existing code, providing structured concurrency that makes managing lifecycle and cancellation much easier than traditional threading.
Correct Answer: Lightweight threads that allow writing asynchronous code in a sequential style
67. What does the suspend modifier on a function mean?
Difficulty: MediumType: MCQTopic: Coroutines
- The function can be paused and resumed, callable only from coroutines or other suspend functions
- The function runs slowly
- The function is deprecated
- The function is private
The suspend keyword marks functions that can suspend execution without blocking the thread, allowing them to be paused and resumed later. Suspend functions can only be called from other suspend functions or from coroutine builders like launch or async, not from regular functions.
When a suspend function calls another suspend function, it can suspend at that point, releasing the thread for other work until the called function completes. This happens automatically without any thread management code from the developer.
Suspend functions are the building blocks of coroutine-based async code, and despite looking like regular sequential code, they compile to efficient state machine implementations that handle suspension and resumption under the hood.
Correct Answer: The function can be paused and resumed, callable only from coroutines or other suspend functions
68. What is the difference between launch and async coroutine builders?
Difficulty: MediumType: MCQTopic: Coroutines
- launch returns Job for fire-and-forget, async returns Deferred for getting results
- launch is slower than async
- async cannot be cancelled
- They are exactly the same
Launch starts a coroutine that returns a Job immediately without blocking, used for fire-and-forget operations where you don't need a result back, like updating UI or logging. Async also starts a coroutine but returns a Deferred which is like a future or promise, and you call await on it to get the result.
Use launch when you don't need a return value and just want to start background work, and async when you need to compute a value and return it. Both can be cancelled through their Job or Deferred, and both support structured concurrency.
Async is useful for parallel operations where you launch multiple async coroutines and await all their results, while launch is better for side effects like database writes or analytics events where you don't wait for completion.
Correct Answer: launch returns Job for fire-and-forget, async returns Deferred for getting results
69. What is Flow in Kotlin Coroutines?
Difficulty: HardType: MCQTopic: Kotlin Flow
- A cold asynchronous stream that emits values sequentially
- A hot observable that immediately starts emitting
- A synchronous collection
- A database query builder
Flow is a cold stream that emits values asynchronously and sequentially, only starting execution when collected, making it perfect for representing streams of data like database queries or network responses. Unlike hot streams that emit regardless of subscribers, Flow only executes when someone collects it.
Flow provides operators like map, filter, and transform similar to collection operations but for async streams, and it handles backpressure automatically by suspending emission when the collector is slow. Flow is built on coroutines using suspend functions.
Use Flow for asynchronous data streams that should be processed one at a time, like reading files, making sequential API calls, or observing database changes, providing a reactive programming model integrated with Kotlin coroutines.
Correct Answer: A cold asynchronous stream that emits values sequentially
70. What does the lazy delegate do in Kotlin?
Difficulty: MediumType: MCQTopic: Delegation
- Delays property initialization until first access with thread-safe caching
- Makes properties load slowly
- Creates lazy people
- Removes properties
The lazy delegate delays property initialization until the property is first accessed, then caches the computed value for subsequent accesses, useful for expensive operations you want to defer until needed. By default, lazy is thread-safe ensuring initialization happens only once even with concurrent access.
Use lazy for properties that are expensive to compute, might not be needed, or depend on state that isn't available during object construction. The initialization lambda runs only once, and the result is stored and returned for all future accesses.
You can customize thread safety with LazyThreadSafetyMode: SYNCHRONIZED for thread-safe default, PUBLICATION allowing multiple initializations but only one wins, or NONE for no synchronization when single-threaded.
Correct Answer: Delays property initialization until first access with thread-safe caching
71. What is the difference between 'out' and 'in' variance modifiers in Kotlin generics?
Difficulty: HardType: MCQTopic: Generics
- out for covariance (producers), in for contravariance (consumers)
- out means output only, in means input only literally
- They control access modifiers
- They are the same
The out modifier makes a generic type covariant, meaning it can only be produced or returned but not consumed as a parameter, like List less than out T greater than allows passing List less than String greater than where List less than Any greater than is expected. Use out for types that produce values, following the producer pattern.
The in modifier makes a type contravariant, allowing consumption but not production, useful for types that only take values as input like Comparable less than in T greater than. This enables passing Comparable less than Any greater than where Comparable less than String greater than is expected.
Use-site variance with out and in provides flexibility when declaration-site variance isn't specified, enabling safe type relationships that would otherwise cause compile errors, and the compiler enforces that out types can't be used in input positions and in types can't be used in output positions.
Correct Answer: out for covariance (producers), in for contravariance (consumers)
72. What does the reified keyword do with inline functions?
Difficulty: HardType: MCQTopic: Reified Types
- Preserves type information at runtime for generic type parameters
- Makes functions run faster
- Removes type checking
- Creates new types
The reified keyword can only be used with inline functions and preserves generic type information at runtime, allowing you to check types with is operator or use class references like T colon colon class. Without reified, generic types are erased at runtime due to Java's type erasure.
Reified enables writing functions that need runtime type information like inline fun less than reified T greater than isType where you can check if a value is of type T, or inline fun less than reified T greater than startActivity to get the class reference. This makes APIs more type-safe and convenient.
The reified keyword works because inline functions copy their code to the call site, so the compiler can substitute the actual type at each call location, eliminating the need for class parameters that generic functions typically require.
Correct Answer: Preserves type information at runtime for generic type parameters
73. What is the main difference between StateFlow and SharedFlow?
Difficulty: HardType: MCQTopic: Kotlin Flow
- StateFlow always has a current value and replays it to new subscribers, SharedFlow is more configurable
- StateFlow is faster
- SharedFlow is deprecated
- They are the same
StateFlow is a hot Flow that always has a current value accessible via value property, replays the most recent value to new collectors immediately, and automatically handles state management with conflation. It's perfect for representing UI state that always has a current value.
SharedFlow is more flexible, allowing configuration of replay count, buffer capacity, and emission strategies, and it doesn't require an initial value. Use SharedFlow for events or when you need more control over behavior.
StateFlow is essentially SharedFlow configured for state management, using replay of 1 and conflation, making it simpler for the common use case of holding and observing mutable state. Choose StateFlow for state, SharedFlow for events or custom streaming requirements.
Correct Answer: StateFlow always has a current value and replays it to new subscribers, SharedFlow is more configurable
74. Explain structured concurrency in Kotlin Coroutines and how it helps manage coroutine lifecycle.
Difficulty: HardType: SubjectiveTopic: Coroutines
Structured concurrency means coroutines are organized in hierarchies where child coroutines are bound to parent scopes, ensuring children are cancelled when parents are cancelled, preventing coroutine leaks. A parent coroutine waits for all children to complete before completing itself, providing automatic cleanup and cancellation propagation.
CoroutineScope defines boundaries for coroutine execution, and launching coroutines within a scope makes them children of that scope. When you cancel a scope or it completes, all child coroutines are automatically cancelled, preventing orphaned background work.
Structured concurrency eliminates manual lifecycle management required with threads or callbacks, automatically handles cancellation propagation, and makes concurrent code easier to reason about. Use viewModelScope in Android or custom scopes with proper lifecycle to ensure coroutines don't leak.
This structure prevents common bugs like continuing background work after the UI is destroyed, ensures proper cleanup, and makes concurrent code more predictable and maintainable through clear parent-child relationships.
75. Explain common Flow operators and when to use each for transforming asynchronous streams.
Difficulty: HardType: SubjectiveTopic: Kotlin Flow
Map transforms each emitted value like collection map but asynchronously, filter selects values based on predicates, and transform provides full control to emit zero or multiple values per input. Use map for one-to-one transformations, filter to skip unwanted values, and transform for flexible emission patterns.
Catch handles upstream exceptions allowing recovery or fallback emissions, onEach performs side effects for each value without transforming it, and combine merges multiple Flows emitting whenever any source emits. Combine is useful for combining UI state from multiple sources.
Buffer controls backpressure by allowing emissions to continue while collection is slow, conflate keeps only the latest value dropping intermediate ones, and collectLatest cancels previous collection when new value arrives. Use conflate for rapidly changing state where intermediate values don't matter.
Debounce emits only after a quiet period, useful for search queries where you want to wait until typing stops. FlatMapConcat, flatMapMerge, and flatMapLatest control how nested Flows are flattened, choosing between sequential, concurrent, or latest-only processing.
76. Explain built-in property delegates in Kotlin including lazy, observable, and vetoable.
Difficulty: MediumType: SubjectiveTopic: Delegation
Lazy delegate defers property initialization until first access with thread-safe caching by default, perfect for expensive computations or properties depending on unavailable state at construction time. Use lazy when initialization is costly and the property might not be used.
Observable delegate from Delegates dot observable notifies a callback whenever the property changes, receiving old and new values, useful for tracking changes or triggering side effects on updates. Use observable for logging, validation, or updating dependent state when properties change.
Vetoable delegate from Delegates dot vetoable is like observable but the callback returns boolean determining whether to accept the change, enabling validation before assignment. If the callback returns false, the property retains its old value, providing declarative validation.
You can create custom delegates implementing getValue and setValue operators, enabling reusable property behavior patterns. Map delegation stores properties in a Map useful for dynamic properties or JSON parsing, and notNull delegate provides late initialization with runtime checks.
77. Explain variance in Kotlin generics including declaration-site and use-site variance with examples.
Difficulty: HardType: SubjectiveTopic: Generics
Declaration-site variance uses out for covariant types that only produce values like interface Producer less than out T greater than, and in for contravariant types that only consume values like interface Consumer less than in T greater than. This variance is specified at the interface or class level and applies everywhere.
Use-site variance applies variance at the point of use with out or in modifiers like function less than T greater than process open paren list colon List less than out T greater than close paren, useful when the type itself isn't variant but you want variance in specific contexts. Star projection less than asterisk greater than represents unknown type when variance doesn't matter.
Covariance with out allows passing List less than String greater than where List less than Any greater than is expected because String is subtype of Any, but you can only read from such lists not add to them. Contravariance with in allows passing Comparator less than Any greater than where Comparator less than String greater than is expected because you can compare Strings using Any comparator.
Type constraints with colon limit generic types like less than T colon Number greater than requiring T to be Number or subtype, and where clauses support multiple constraints. Understanding variance prevents type errors and enables writing flexible generic code.
78. How do inline functions and reified type parameters work together and what are their use cases?
Difficulty: HardType: SubjectiveTopic: Reified Types
Inline functions have their code copied to the call site instead of creating function calls, eliminating overhead for small functions especially higher-order functions with lambdas. When you make generic functions inline, you can use reified modifier to preserve type information at runtime, enabling runtime type checks and class references.
Reified works because inlining substitutes actual type at each call location, so inline fun less than reified T greater than filterIsInstance can check if elements are of type T using is operator. Without reified, generic types are erased at runtime requiring explicit class parameters.
Common use cases include extension functions like startActivity less than reified T colon Activity greater than in Android where T provides the Activity class to start, filterIsInstance for type-safe collection filtering, and JSON parsing where type information determines deserialization. Reified enables cleaner APIs without explicit class parameters.
Inline with reified has costs: increased code size from copying function body to each call site, and inability to store reified functions in variables or pass them as non-inline parameters. Use judiciously for small frequently-called functions where benefits outweigh costs.
79. How do you create type-safe DSL builders in Kotlin using lambda receivers and extension functions?
Difficulty: HardType: SubjectiveTopic: DSL Builders
DSL builders use lambda with receiver syntax where the lambda has a receiver type making this refer to that type inside the lambda body, like html open curly body open curly div open curly plus text close curly close curly close curly. The receiver type provides context and available functions creating a domain-specific language.
Create DSLs by defining builder classes with extension functions and using function types with receivers like HTML dot open paren close paren arrow Unit. Mark DSL functions with at DslMarker annotation preventing implicit this from outer scopes, ensuring only the current scope's functions are accessible without qualification.
Use apply, with, or custom functions returning receivers to chain builder calls fluently. Restrict function visibility with annotations or internal modifiers to prevent calling builder functions outside their intended context, maintaining type safety throughout the DSL.
DSL builders are powerful for creating readable configuration APIs like Gradle build scripts written in Kotlin, HTML builders, or test setup code. They make complex nested structures readable and provide compile-time safety unlike string-based or reflection-based approaches.
80. Explain CoroutineContext, CoroutineScope, and the different Dispatchers in Kotlin Coroutines.
Difficulty: HardType: SubjectiveTopic: Dispatchers
CoroutineContext is a set of elements defining coroutine behavior including the Job for lifecycle management, CoroutineDispatcher for thread execution, CoroutineName for debugging, and exception handler. Each coroutine has a context inherited from its parent with possible overrides.
CoroutineScope defines the lifecycle boundary for launched coroutines, combining CoroutineContext with structured concurrency. Create scopes with CoroutineScope open paren context close paren or use predefined scopes like GlobalScope for application lifetime though usually avoid it preferring scoped instances.
Dispatchers determine which thread or thread pool executes coroutine code: Dispatchers dot Main for UI thread in Android, Dispatchers dot IO for IO operations like network or disk with large thread pool, Dispatchers dot Default for CPU-intensive work with threads equal to CPU cores, and Dispatchers dot Unconfined for testing running on current thread.
Switch contexts with withContext to run code on different dispatchers like withContext open paren Dispatchers dot IO close paren for IO work then automatically return to the calling context. Understanding these concepts is crucial for writing efficient, non-blocking concurrent code with proper lifecycle management.
81. How do extension properties work in Kotlin and what are their limitations compared to regular properties?
Difficulty: MediumType: SubjectiveTopic: Extensions
Extension properties add properties to existing types without modifying them, declared like extension functions with the type before the property name like val String dot lastChar colon Char get open paren close paren equals get open paren length minus 1 close paren. They must have custom getters since they cannot have backing fields.
Extension properties cannot store state because they don't have backing fields, so they must compute values from the receiver object's existing state or be constant. Var extension properties require both getter and setter, and the setter receives the new value to validate or transform before applying to receiver properties.
Use extension properties for computed values derived from existing properties like collection dot isEmpty as extension of List, or for providing convenient property syntax for methods. They make code more readable by allowing property syntax instead of method calls for side-effect-free computed values.
Limitations include no backing fields, no initialization, cannot override in subclasses since resolved statically, and cannot access private members. Despite limitations, extension properties improve API expressiveness for calculated values that conceptually feel like properties.