Interfaces Workbook

Ten short exercises on Kotlin interfaces — implementing them, default methods, property contracts, resolving clashes between defaults, functional interfaces, and interface inheritance.

Practice problems for Kotlin Interfaces Carry More Than Java’s Ever Could. Each takes a minute or two. Many exercises auto-check: implement the method or property and press Run — hidden tests go green when you’re right and red (with a hint) when you’re not, on JetBrains’ Kotlin server. The declaration exercises stay attempt-then-reveal: press Run to compile, then click Show answer to check yourself. Nothing here is a trick question, just direct practice of the syntax from the lesson.

declaring and implementing

1. Implement an interface

Declare interface Greeter { fun greet(name: String): String } and a class Formal that implements it.

Try it — edit, then press Run interface Greeter { fun greet(name: String): String } fun main() { // TODO: make Formal implement Greeter and override greet class Formal { fun greet(name: String) = "Good evening" } println(Formal().greet("Ada")) }
Show answer Hide answer
interface Greeter {
    fun greet(name: String): String
}

class Formal : Greeter {
    override fun greet(name: String) = "Good evening, $name."
}

A class adopts an interface with : — there’s no implements keyword — and override is required.

2. A default method

Add a greetAll(names: List<String>) default method to Greeter that greets each name, built on greet.

Try it — edit, then press Run interface Greeter { fun greet(name: String): String // TODO: add a default greetAll(names: List<String>) built on greet } class Formal : Greeter { override fun greet(name: String) = "Hi " + name } fun main() { val g = Formal() println(g.greet("Ada")) // once greetAll exists: println(g.greetAll(listOf("Ada", "Grace"))) }
Show answer Hide answer
interface Greeter {
    fun greet(name: String): String

    fun greetAll(names: List<String>) =
        names.joinToString("\n") { greet(it) }
}

3. A property contract

Implement the derived val label on Named so it returns "Name: <name>", built on the required val name.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun label() { Assert.assertEquals("label reads 'Name: <name>'", "Name: Ada", User("Ada").label) } } //sampleStart interface Named { val name: String val label: String get() = "" // TODO: return "Name: " + name } class User(override val name: String) : Named //sampleEnd
Show answer Hide answer
interface Named {
    val name: String
    val label: String
        get() = "Name: $name"
}

4. Provide the property

Write class User(...) implementing Named by supplying name through its constructor.

Try it — edit, then press Run interface Named { val name: String val label: String get() = "Name: " + name } fun main() { // TODO: write class User implementing Named, supplying name via the constructor class User(val name: String) println(User("Ada").name) }
Show answer Hide answer
class User(override val name: String) : Named

label is inherited; only name must be provided.

clashes and functional interfaces

5. Resolve a clash

interface A { fun ping() = "A" } and interface B { fun ping() = "B" }. Implement C.ping() so it returns "AB" by calling both inherited defaults.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun ping() { Assert.assertEquals("combine both defaults into 'AB'", "AB", C().ping()) } } //sampleStart interface A { fun ping() = "A" } interface B { fun ping() = "B" } class C : A, B { override fun ping() = "A" // TODO: return "AB" using super<A>.ping() + super<B>.ping() } //sampleEnd
Show answer Hide answer
class C : A, B {
    override fun ping() = super<A>.ping() + super<B>.ping()
}

When two defaults clash the compiler forces you to choose, using super<Interface>.

6. A functional interface

Declare a fun interface Validator { fun isValid(s: String): Boolean } and create a validator for non-blank strings using a lambda.

Try it — edit, then press Run fun interface Validator { fun isValid(s: String): Boolean } fun main() { // TODO: make a Validator for non-blank strings using a lambda val notBlank = Validator { false } println(notBlank.isValid("hi")) println(notBlank.isValid(" ")) }
Show answer Hide answer
fun interface Validator {
    fun isValid(s: String): Boolean
}

val notBlank = Validator { it.isNotBlank() }

inheritance and choices

7. Interface extends interfaces

Given interface Named { val name: String } and interface Aged { val age: Int }, declare interface Person that combines both and adds a default describe().

Try it — edit, then press Run interface Named { val name: String } interface Aged { val age: Int } // TODO: declare interface Person combining Named and Aged with a default describe() class Robot(override val name: String, override val age: Int) : Named, Aged fun main() { val r = Robot("R2", 5) println(r.name + " is " + r.age) }
Show answer Hide answer
interface Person : Named, Aged {
    fun describe() = "$name, $age"
}

8. Implement two interfaces

Declare class Robot implementing both Named and Aged (each with one property).

Try it — edit, then press Run interface Named { val name: String } interface Aged { val age: Int } fun main() { // TODO: declare class Robot implementing BOTH Named and Aged class Robot(val name: String, val age: Int) val r = Robot("R2", 5) println(r.name + " is " + r.age) }
Show answer Hide answer
class Robot(override val name: String, override val age: Int) : Named, Aged

A class can implement any number of interfaces.

9. Shared stored state

Implementers need to share a stored cache field. Write the base type as the kind that can hold one, with an abstract load(key: String): String.

Try it — edit, then press Run fun main() { // TODO: this base type must hold a shared stored 'cache' field + an abstract load(key) // interface or abstract class? abstract class Repository { abstract fun load(key: String): String } class MemRepo : Repository() { override fun load(key: String) = "value for " + key } println(MemRepo().load("a")) }
Show answer Hide answer
abstract class Repository {
    protected val cache = mutableMapOf<String, String>()
    abstract fun load(key: String): String
}

An interface can declare a property but can’t hold a backing field, so shared stored state calls for an abstract class.

10. A default built on abstract members

Declare interface Counter with an abstract count(): Int and a default isEmpty() returning whether the count is zero.

Try it — edit, then press Run interface Counter { fun count(): Int // TODO: add a default isEmpty() returning whether count() is zero } class Fixed(val n: Int) : Counter { override fun count() = n } fun main() { val c = Fixed(0) println(c.count()) // once isEmpty exists: println(c.isEmpty()) }
Show answer Hide answer
interface Counter {
    fun count(): Int
    fun isEmpty() = count() == 0
}

Going deeper: clashes and SAM

12. Resolve the clash

C implements both A and B, each with a default ping(). Override ping in C to return "AB" by calling both parents.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert interface A { fun ping() = "A" } interface B { fun ping() = "B" } class Test { @Test fun clash() { Assert.assertEquals("AB", C().ping()) } } //sampleStart class C : A, B { // TODO: override ping() to call both parents' defaults } //sampleEnd
Show answer Hide answer
class C : A, B {
    override fun ping() = super<A>.ping() + super<B>.ping()
}

When two interfaces supply the same default, Kotlin forces you to resolve it explicitly with super<Interface> — no silent winner.

13. A lambda as an implementation

Mark Validator so a lambda can stand in for an implementation, then create one that accepts non-blank strings.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun validator() { Assert.assertTrue(notBlank().ok("hi")) Assert.assertFalse(notBlank().ok(" ")) } } //sampleStart interface Validator { fun ok(s: String): Boolean } // TODO: make this accept a lambda fun notBlank(): Validator = object : Validator { override fun ok(s: String) = false } // TODO: simplify to a lambda //sampleEnd
Show answer Hide answer
fun interface Validator { fun ok(s: String): Boolean }
fun notBlank(): Validator = Validator { it.isNotBlank() }

fun interface (a single abstract method) lets a lambda become the implementation — Kotlin’s SAM conversion for its own types.


Back to the lesson, Kotlin Interfaces Carry More Than Java’s, or on to the next one: extension functions.

Comments