Interfaces & Abstract Classes

Reviewed & published by Brayan K

An interface is a contract of capabilities a type can declare and optionally implement, while an abstract class is a partially-implemented base that can also hold state and a constructor.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

You'll learn abstract and default methods, properties in interfaces, implementing multiple interfaces, when to choose an abstract class, and resolving conflicts with super<Type>.

What You'll Learn in This Lesson

1️⃣ Interfaces: Abstract + Default Members

An interface declares what a type can do. A method with no body is abstract — every implementer must provide it. A method with a body is a default implementation that implementers inherit for free. Interfaces can also declare properties, though those must be abstract or computed (no stored value).

// An interface declares WHAT a type can do. Methods can be abstract
// (no body) or have a DEFAULT implementation that classes inherit.
interface Greeter {
    val name: String            // abstract property — implementers must provide it
    fun greet(): String         // abstract method — implementers must write it

    fun greetLoudly(): String { // default method — implementers may reuse it
        return greet().uppercase()
    }
}

class FriendlyGreeter(override val name: String) : Greeter {
    override fun greet(): String = "Hi, I'm $name"
}

fun main() {
    val g = FriendlyGreeter("Ada")
    println(g.greet())          // -> Hi, I'm Ada
    println(g.greetLoudly())    // -> HI, I'M ADA
}

The override keyword is required when implementing an interface member, and the default greetLoudly() reuses the abstract greet() without the class writing it again.

2️⃣ Implementing Multiple Interfaces

A class can implement several interfaces at once — that's how Kotlin composes independent behaviours despite allowing only one superclass. If two interfaces provide a default method with the same signature, the compiler forces you to override it and pick which implementation to use via super<Interface>.

// A class can implement MANY interfaces (unlike single inheritance).
interface Swimmer {
    fun move(): String = "swimming"
}
interface Flyer {
    fun move(): String = "flying"
}

// When two interfaces give the SAME default method, you MUST override it
// and pick which one(s) to use with super<Interface>.
class Duck : Swimmer, Flyer {
    override fun move(): String {
        return super<Swimmer>.move() + " and " + super<Flyer>.move()
    }
}

fun main() {
    val duck = Duck()
    println(duck.move())   // -> swimming and flying
}

Inside the override you can call one, the other, or both qualified supers — here Duck.move() combines them.

3️⃣ Abstract Classes: State + Constructor

An abstract class goes further than an interface: it can store real properties, run a constructor, and keep internal state, while still leaving some members abstract for subclasses to implement. Use one when subclasses share state or setup logic; use an interface when you only need to declare capabilities or mix several together.

// An ABSTRACT class can hold state and a constructor, and mixes
// abstract members (subclasses must implement) with concrete ones.
abstract class Shape(val name: String) {
    abstract fun area(): Double          // no body — subclasses provide it

    fun describe(): String {             // concrete — shared by all shapes
        return "$name has area ${area()}"
    }
}

class Rectangle(val w: Double, val h: Double) : Shape("Rectangle") {
    override fun area(): Double = w * h
}

fun main() {
    val r = Rectangle(3.0, 4.0)
    println(r.area())        // -> 12.0
    println(r.describe())    // -> Rectangle has area 12.0
}

Shape holds a name and provides a concrete describe(), while each subclass supplies its own area() — something a pure interface couldn't do with stored state.

Your turn. Fill in the ___, then run and compare.

// 🎯 YOUR TURN — replace the ___ then run main()
interface Clickable {
    fun click(): String
    fun describe(): String = "A clickable thing"   // default method
}

// Implement the interface. Use the right keyword on the method.
class Button : Clickable {
    ___ fun click(): String = "clicked!"   // 👉 put "override" in the blank
}

fun main() {
    val b = Button()
    println(b.click())       // ✅ Expected: clicked!
    println(b.describe())    // ✅ Expected: A clickable thing
}

Mini-Challenge: Shapes with an Interface

Define an interface with one abstract method and one default method, then implement it.

// 🎯 MINI-CHALLENGE: shapes with an interface.
// Save as Shapes.kt and run main().
//
// 1. Define an interface Shape with an abstract fun area(): Double
//    and a DEFAULT fun label(): String that returns "area = " + area().
// 2. Make a class Square(val side: Double) that implements Shape,
//    where area() returns side * side.
// 3. Create Square(5.0) and print area() then label().
//
// ✅ Expected output:
//    25.0
//    area = 25.0

fun main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Interfaces & Abstract Classes

ConceptKotlin Syntax
Declare interfaceinterface Greeter { ... }
Default methodfun greetLoudly(): String { ... }
Implement itclass C : Greeter { ... }
Multiple interfacesclass Duck : Swimmer, Flyer
Disambiguatesuper<Swimmer>.move()
Abstract classabstract class Shape(...)

🎉 Lesson Complete!

Practice quiz

An interface method with no body is…

  • abstract — implementers must provide it
  • a default method
  • a private method
  • automatically static

Answer: abstract — implementers must provide it. A method without a body is abstract; every implementer must supply it.

What is a default method in an interface?

  • A method that returns null
  • A method with a body that implementers inherit for free
  • A method that cannot be overridden
  • The first method declared

Answer: A method with a body that implementers inherit for free. A default method provides a body that implementers inherit unless they override it.

What restriction applies to properties declared in an interface?

  • They must be private
  • They must be var
  • They cannot store a value in a backing field
  • They must be nullable

Answer: They cannot store a value in a backing field. Interface properties must be abstract or computed — no backing field/stored value.

Which keyword is required when implementing an interface member?

  • open
  • implement
  • super
  • override

Answer: override. Implementing an interface member requires the override keyword.

How many interfaces can a single class implement?

  • As many as needed
  • Exactly one
  • At most two
  • None

Answer: As many as needed. A class can implement many interfaces, though it extends only one class.

If two interfaces supply the same default method, what must the class do?

  • Ignore both
  • Override it and disambiguate the super call
  • Delete one interface
  • Nothing — Kotlin picks one

Answer: Override it and disambiguate the super call. The class must override the method and pick which default via super<Interface>.

What does super<Swimmer>.move() do?

  • Creates a new Swimmer
  • Calls the Swimmer interface's default move()
  • Makes move() abstract
  • Returns null

Answer: Calls the Swimmer interface's default move(). It calls the move() default implementation specifically from the Swimmer interface.

What can an abstract class do that an interface cannot?

  • Declare abstract methods
  • Provide default method bodies
  • Be a supertype
  • Hold stored state and have a constructor

Answer: Hold stored state and have a constructor. Abstract classes can store real properties and run a constructor; interfaces cannot.

When should you prefer an interface over an abstract class?

  • When you need shared stored state
  • When you want to mix several independent capabilities
  • When you need a constructor
  • When you need a backing field

Answer: When you want to mix several independent capabilities. Interfaces compose freely, so prefer them for mixing multiple capabilities.

Which is a valid interface property?

  • val count: Int = 5
  • val ready: Boolean get() = true
  • private var x = 0
  • lateinit var s: String

Answer: val ready: Boolean get() = true. A computed property with a getter is allowed; a stored value with a backing field is not.

Continue this course

Frequently asked questions

What is the difference between an interface and an abstract class?

An interface describes a set of capabilities and can declare abstract methods, default method bodies, and abstract properties — but it cannot hold state with a backing field or have a constructor. An abstract class can do all that plus store real properties, run constructor logic, and keep internal state. The biggest practical difference is that a class may implement many interfaces but inherit from only one class, so reach for interfaces when you want to mix in several behaviours, and an abstract class when you need shared state or a common constructor.

Can an interface have a default method body?

Yes. A method in a Kotlin interface can provide a default implementation that all implementers inherit unless they override it. This lets you offer ready-made behaviour built on top of the abstract members — for example greetLoudly() calling the abstract greet(). Interfaces can also declare properties, but those properties cannot store a value directly (no backing field); they must be abstract or computed from a getter.

Why would a class implement more than one interface?

Kotlin allows only single class inheritance, but a type often needs several independent capabilities — something might be both Clickable and Draggable, or both Swimmer and Flyer. Implementing multiple interfaces composes those behaviours without forcing an artificial class hierarchy. If two interfaces happen to supply the same default method, the compiler makes you override it and disambiguate which one you want using super&lt;InterfaceName&gt;.

What does super<Interface> do?

When a class inherits the same-named method from two interfaces, calling super.method() is ambiguous. The qualified syntax super<Swimmer>.move() tells the compiler exactly which interface's default implementation you mean. You can call one, the other, or both inside your override — which is how the Duck example combines swimming and flying into a single move() result.