Kotlin classes and data classes interview questions, with answers
Kotlin classes remove most of the boilerplate Java classes carry. A primary constructor declares and initialises properties in one line, properties replace getter and setter methods, and a data class gets equals, hashCode, toString and copy from the compiler. Interviewers ask what exactly the compiler generates, how construction and initialisation are ordered, and which kinds of class suit which job.
The answers below cover data classes and copy, constructors and init blocks, properties, enum classes, value classes and visibility modifiers, with code compiled with kotlinc 2.3 and run on the JVM. Then take the free Kotlin diagnostic — ten questions across every Kotlin topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.What is a data class in Kotlin, and what does the compiler generate for it?
In short: A class marked data gets equals, hashCode, toString, copy and componentN functions generated from the properties in its primary constructor.
data class Pt(val x: Int, val y: Int) is a complete value type in one declaration. The compiler generates equals and hashCode that compare the constructor properties, so two Pt values with the same coordinates are equal and work as map keys; a readable toString, Pt(x=1, y=2); a copy function; and component1, component2 and so on for destructuring. A data class needs at least one parameter in its primary constructor, every one declared val or var, and it cannot be abstract, open or sealed. Properties declared in the class body are ignored by all the generated functions.
data class Pt( val x: Int, val y: Int, ) fun main() { val p = Pt(1, 2) println(p) // Pt(x=1, y=2) println(p == Pt(1, 2)) // true }
2.What does copy do on a Kotlin data class?
In short: copy creates a new instance with the same property values, except those you pass as named arguments, which is how immutable data is updated.
With read-only properties, a data class value cannot change, so updating it means making a modified copy: a.copy(pay = 12) returns a new Job with every property taken from a except pay. The original is untouched, which is why a and b below compare unequal. Named arguments let any subset of properties be changed in one call. This pattern is central to state handling in Android and other UI code, where each new state is a copy of the previous one with a few fields changed, and comparing old and new states is a cheap equals call.
data class Job( val role: String, val pay: Int, ) fun main() { val a = Job("dev", 10) val b = a.copy(pay = 12) println(b.pay) // 12 println(a == b) // false }
3.What are primary constructors and init blocks in Kotlin?
In short: The primary constructor is part of the class header and can declare properties directly; init blocks run code during construction, such as validation.
class Acct(val owner: String) declares a class, its constructor and an owner property at once. Parameters without val or var are plain constructor parameters, available to property initialisers and init blocks. Code that must run when the object is created goes in init blocks, which run in order together with the property initialisers; require is the idiomatic way to validate arguments there, throwing an IllegalArgumentException for bad input. A property can also restrict its setter, as balance does below with private set, so it can be read from outside but changed only by the class's own methods. Secondary constructors must delegate to the primary one.
class Acct(val owner: String) { var balance = 0 private set init { require(owner != "") } fun deposit(n: Int) { balance += n } } fun main() { val a = Acct("ria") a.deposit(50) println(a.balance) // 50 }
4.How do properties, getters and setters work in Kotlin?
In short: A property combines a field with accessors; the compiler generates default getters and setters, and a custom getter or setter can compute or validate the value.
Kotlin classes declare properties rather than fields. val c: Double gets a generated getter and var a getter and a setter, and callers use plain property syntax, t.c, while the JVM sees getC and setC methods. A custom getter, as for f below, computes the value on each access and needs no stored field, so f always reflects the current c. A custom setter can validate or react to changes, referring to the stored value as field. Because access always goes through accessors, a simple property can later gain logic without changing any caller.
class Temp(var c: Double) { val f: Double get() = c * 9 / 5 + 32 } fun main() { val t = Temp(100.0) println(t.f) // 212.0 t.c = 0.0 println(t.f) // 32.0 }
5.What are enum classes in Kotlin?
In short: An enum class defines a fixed set of named constants, each an object that can carry properties and methods, with built-ins such as name, ordinal, valueOf and entries.
enum class Dir(val dx: Int) declares a type whose only values are the listed constants, each constructed with its own arguments. Every constant has a name and an ordinal, its position from zero, and the class provides valueOf("LEFT"), which throws for an unknown name, and entries, the list of all constants, replacing the older values() array. Enums can declare methods, including abstract ones overridden per constant, and implement interfaces. A when expression over an enum is exhaustive without an else branch when every constant is covered, as with sealed classes.
enum class Dir(val dx: Int) { LEFT(-1), RIGHT(1); } fun main() { val d = Dir.valueOf("LEFT") println(d.dx) // -1 println(Dir.entries.size) // 2 }
6.What are value classes in Kotlin?
In short: A value class wraps a single value in its own type for type safety, and the compiler usually represents it as the bare underlying value, with no object allocated.
Passing raw strings or numbers for different meanings, such as an email and a name, lets them be mixed up. @JvmInline value class Mail(val s: String) gives the email its own type, so send(m: Mail) cannot receive an ordinary string by mistake. At run time the compiler replaces Mail with the String itself wherever it can, so the extra safety costs no allocation; the value is boxed only when used as a nullable or generic type. A value class has exactly one property in its primary constructor, cannot have identity (=== is not allowed), and can have methods and init blocks.
@JvmInline value class Mail(val s: String) fun send(m: Mail) = "to ${m.s}" fun main() { val m = Mail("a@b.in") println(send(m)) // to a@b.in }
7.What are Kotlin's visibility modifiers?
In short: public is the default, private limits a declaration to its class or file, protected adds subclasses, and internal limits it to the same module.
Unlike Java, Kotlin makes declarations public when no modifier is given. private restricts a class member to that class, or a top-level declaration to its file. protected, for members only, also allows subclasses. internal means visible anywhere in the same module, a set of files compiled together such as one Gradle module, which suits helpers shared inside a library but hidden from its users. There is no package-private visibility. In the code, code is private, so it can be used by ok but not read from main, while the internal name is readable because main is in the same module.
class Vault { private val code = 42 internal val name = "v1" fun ok(n: Int) = n == code } fun main() { val v = Vault() println(v.ok(42)) // true println(v.name) // v1 }
How the diagnostic asks it
One question from the Kotlin bank, exactly as a sitting would show it. The bank has 5 on classes & data classes and 30 across Kotlin.
What does this Kotlin program print?
data class User(val id: Int) { var visits = 0 } fun main() { val a = User(7) val b = User(7) a.visits = 5 println("${a == b} $a") }
- 1true User(id=7)correct
- 2false User(id=7, visits=5)
- 3true User(id=7, visits=5)
- 4false User(id=7)
The members a data class generates, equals, hashCode, toString, copy and componentN, are built only from the properties declared in the primary constructor. visits is declared in the class body, so it is invisible to them: a and b are equal because their ids are equal, even though a.visits is 5, and toString prints User(id=7) without visits. The options showing visits=5 assume every property counts, and the ones with false assume visits affects equality. Declaring a property in the body is how to keep it out of equality, but it is also easy to do by accident.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 Kotlin questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.