Skip to content

Kotlin Interfaces — Complete Guide with Examples

DodaTech Updated 2026-06-28 9 min read

In this tutorial, you will learn about Kotlin Interfaces. We cover key concepts, practical examples, and best practices to help you master this topic.

Kotlin interfaces can declare abstract and default methods, abstract properties, and support multiple inheritance with Conflict Resolution through override and super with angle bracket syntax.

What You'll Learn

  • Declare interfaces with abstract and default implementations
  • Implement multiple interfaces in a single class
  • Resolve method conflicts between interfaces
  • Define abstract properties in interfaces
  • Use functional (SAM) interfaces for single-method contracts
  • Apply interface delegation with the by keyword
  • Distinguish between interfaces and abstract classes

Why It Matters

Interfaces define contracts that classes can implement. Kotlin interfaces are more powerful than Java's because they can contain property declarations and default method implementations. Multiple interface inheritance allows a class to conform to multiple contracts without the diamond problem ambiguities. Interface delegation reduces boilerplate by automatically generating forwarding methods. Understanding interfaces is key to designing loosely coupled, testable systems.

Real-World Use

DodaTech's Android app uses interfaces for Repository patterns, where data sources implement Readable and Writable interfaces. The logger system defines a Loggable interface with default formatting. Multiple components (FileLogger, ConsoleLogger, CrashReporter) each implement Logger with specific behavior.

Learning Path

flowchart LR
  A[Inheritance] --> B[Interfaces\nYou are here]
  B --> C[Data Classes]
  style B fill:#f90,color:#fff

Interface Declaration

Interfaces are declared with the interface keyword. Methods can be abstract or have default implementations.

interface Drawable {
    fun draw()
    
    fun description(): String {
        return "A drawable object"
    }
}

interface Resizable {
    fun resize(factor: Double)
    val isResizable: Boolean
}

class Circle(val radius: Double) : Drawable, Resizable {
    override fun draw() {
        println("Drawing circle with radius $radius")
    }
    
    override fun resize(factor: Double) {
        // Resize logic
    }
    
    override val isResizable: Boolean get() = true
    
    override fun description(): String {
        return "Circle with radius $radius"
    }
}

fun main() {
    val circle = Circle(5.0)
    circle.draw()              // Output: Drawing circle with radius 5.0
    println(circle.description())  // Output: Circle with radius 5.0
    println("Resizable: ${circle.isResizable}")  // Output: Resizable: true
}

Output: Circle implements both Drawable and Resizable interfaces, providing concrete implementations for abstract members and optionally overriding default methods.

Properties in Interfaces

Interfaces can declare abstract properties. Implementing classes must override them.

interface Configurable {
    val name: String
    val timeout: Int
    val retries: Int
        get() = 3  // Default implementation
    
    val fullDescription: String
        get() = "$name (timeout=${timeout}s, retries=$retries)"
}

class DatabaseConfig(override val name: String) : Configurable {
    override val timeout: Int = 30
    // retries uses the default from the interface
}

class NetworkConfig(override val name: String) : Configurable {
    override val timeout: Int = 10
    override val retries: Int = 5  // Override the default
}

fun main() {
    val dbConfig = DatabaseConfig("PostgreSQL")
    println(dbConfig.fullDescription)  // Output: PostgreSQL (timeout=30s, retries=3)
    
    val netConfig = NetworkConfig("API Service")
    println(netConfig.fullDescription)  // Output: API Service (timeout=10s, retries=5)
}

Output: Interface properties can have default getters. Implementing classes override abstract properties but can use or override default implementations.

Interface properties cannot store state (no backing fields). All properties are either abstract or computed.

Multiple Interface Inheritance

A class can implement multiple interfaces. The compiler handles conflicts when two interfaces define the same method.

interface A {
    fun method() = println("A's method")
    fun another()
}

interface B {
    fun method() = println("B's method")
    fun extra()
}

class Impl : A, B {
    // Must resolve conflict between A.method() and B.method()
    override fun method() {
        super<A>.method()
        super<B>.method()
        println("Impl's additional logic")
    }
    
    override fun another() = println("Implementing another")
    override fun extra() = println("Implementing extra")
}

fun main() {
    val impl = Impl()
    impl.method()
    impl.another()
    impl.extra()
}

Output:

A's method
B's method
Impl's additional logic
Implementing another
Implementing extra

When two interfaces define the same method, the implementing class must override it. Inside the override, use super to call specific interface implementations.

SAM (Functional) Interfaces

A Single Abstract Method interface is a functional interface with one abstract method. Use fun interface keyword for SAM conversion.

fun interface ClickListener {
    fun onClick(view: String)
}

// SAM conversion: lambda instead of anonymous class
fun interface StringTransformer {
    fun transform(input: String): String
}

fun main() {
    // Traditional anonymous object
    val listener1 = object : ClickListener {
        override fun onClick(view: String) {
            println("Clicked on $view")
        }
    }
    
    // SAM conversion with lambda
    val listener2 = ClickListener { view ->
        println("Lambda handling click on $view")
    }
    
    listener1.onClick("Button")  // Output: Clicked on Button
    listener2.onClick("Button")  // Output: Lambda handling click on Button
    
    // Another SAM example
    val toUpper = StringTransformer { it.uppercase() }
    val reverse = StringTransformer { it.reversed() }
    
    println(toUpper.transform("hello"))  // Output: HELLO
    println(reverse.transform("hello"))  // Output: olleh
    
    // Using with collection
    val numbers = listOf(1, 2, 3, 4, 5)
    val predicate = fun interface Predicate {
        fun test(x: Int): Boolean
    }
    
    val isEven = Predicate { it % 2 == 0 }
    println(numbers.filter { isEven.test(it) })  // Output: [2, 4]
}

Output: SAM interfaces allow lambda syntax where anonymous classes were required in Java. This reduces boilerplate significantly.

Interface Delegation

The by keyword enables you to delegate interface implementation to another object.

interface Loggable {
    fun log(message: String)
    fun logError(message: String) {
        log("ERROR: $message")
    }
}

class ConsoleLogger : Loggable {
    override fun log(message: String) {
        println("[Console] $message")
    }
}

class FileLogger : Loggable {
    override fun log(message: String) {
        println("[File] $message")
    }
}

// Delegation: CustomLogger delegates Loggable to ConsoleLogger
class CustomLogger(logger: Loggable = ConsoleLogger()) : Loggable by logger {
    override fun log(message: String) {
        // Add timestamp before delegating
        println("[${System.currentTimeMillis()}] Custom: $message")
        // Call the delegate's implementation
    }
}

fun main() {
    val console = ConsoleLogger()
    console.log("Hello")  // Output: [Console] Hello
    
    val custom = CustomLogger()
    custom.log("Delegated")  // Output with timestamp and "Custom: Delegated"
    
    // Delegation to FileLogger
    val fileLogger = FileLogger()
    val customFile = CustomLogger(fileLogger)
    customFile.logError("File not found")  // Output with timestamp and "Custom: ERROR: File not found"
}

Output: The CustomLogger class implements Loggable by delegating all methods to the provided logger instance. Override specific methods to add behavior before or after delegation.

Interface versus Abstract Class

Feature Interface Abstract Class
Constructor No Yes
State (backing fields) No Yes
Multiple inheritance Yes No
Access modifiers No private members All modifiers
Default methods Yes Yes
Instantiation No No

Use interfaces when you define a contract (what something can do). Use abstract classes when you define a partial implementation with shared state.

Common Mistakes

  1. Trying to store state in interfaces: Interface properties cannot have backing fields. All properties must be abstract or computed. Use an abstract class if you need mutable state.

  2. Using interface where abstract class is appropriate: If multiple related classes share implementation code and state, use an abstract class. Interfaces are for contracts, not implementation sharing.

  3. Not handling multiple inheritance conflicts: When two interfaces define the same method, the implementing class must override it. The compiler does not choose a default.

  4. Forgetting fun interface for SAM conversion: Regular interfaces with one abstract method do not automatically support SAM conversion. Add fun before interface to enable lambda syntax.

  5. Overusing interface delegation: Delegation adds indirection. Use it when the delegation relationship is natural (e.g., logger wrapping another logger).

  6. Creating interfaces with too many methods: Interfaces should be focused (Interface Segregation Principle). Split large interfaces into smaller, role-specific ones.

Practice Questions

  1. Can an interface have default method implementations?

Answer: Yes. Interface methods can have default implementations. Implementing classes can use the default or override it.

  1. How do you resolve a conflict when two interfaces define the same method?

Answer: Override the conflicting method in the implementing class. Use super to call specific interface implementations.

  1. What is SAM conversion and when does it apply?

Answer: SAM conversion allows using a lambda instead of an anonymous class for interfaces with a single abstract method. The interface must be declared with fun interface.

  1. What is interface delegation with the by keyword?

Answer: The by keyword delegates all interface methods to a provided object. The class can override specific methods while delegating others.

  1. Challenge: Create a filtering system with interfaces. Define Filter, Sortable, and Paginable interfaces. Implement a DataProcessor class that uses interface delegation for filtering and sorting, and provides its own pagination logic.

Answer:

interface Filter<T> {
    fun apply(items: List<T>): List<T>
}

interface Sortable<T> {
    fun sort(items: List<T>): List<T>
}

class NameFilter(private val query: String) : Filter<String> {
    override fun apply(items: List<String>): List<String> {
        return items.filter { it.contains(query, ignoreCase = true) }
    }
}

class AlphabeticalSort : Sortable<String> {
    override fun sort(items: List<String>): List<String> {
        return items.sorted()
    }
}

class DataProcessor<T>(
    private val filter: Filter<T>,
    private val sorter: Sortable<T>,
    private val pageSize: Int = 10
) : Filter<T> by filter, Sortable<T> by sorter {
    
    fun process(items: List<T>, page: Int = 1): List<T> {
        val filtered = filter.apply(items)
        val sorted = sorter.sort(filtered)
        val start = (page - 1) * pageSize
        return sorted.drop(start).take(pageSize)
    }
}

fun main() {
    val items = listOf("Banana", "Apple", "Cherry", "apricot", "Blueberry")
    val processor = DataProcessor(NameFilter("a"), AlphabeticalSort(), 3)
    val result = processor.process(items)
    println(result)  // Output: [Apple, apricot, Banana]
}

Mini Project

Build a plugin system using interfaces. Requirements:

  • Plugin interface with name(), init(), execute(), and cleanup() methods
  • Default implementations for init() and cleanup()
  • PluginMananger interface with register, unregister, and executeAll
  • Use interface delegation for PluginManager
  • Each plugin implements Plugin with custom execute logic
  • Use SAM interfaces for simple single-method plugins

This project demonstrates interface design, default methods, delegation, and SAM conversion in a practical plugin architecture.

FAQ

Can an interface extend another interface?

Yes, interfaces can extend other interfaces using the colon syntax: 'interface MyInterface : BaseInterface'. The implementing class must implement all inherited methods.

Can an interface have a companion object?

Yes. Interfaces can have companion objects, which provide a place for static utility functions and constants related to the interface.

Are Kotlin interfaces the same as Java interfaces?

Kotlin interfaces support default methods (like Java 8+), property declarations, and SAM conversion. Unlike Java, Kotlin interfaces cannot have backing fields.

What is the difference between implementing an interface and extending a class?

A class can implement multiple interfaces but extend only one class. Interfaces provide contracts; classes provide implementation inherited via subclassing.

Can I declare a method as private in an interface?

No. Interface members cannot be private. They are public by default. Use an abstract class with private methods if you need encapsulation.

What's Next

After learning interfaces, explore data classes for concise model objects. You can also learn about objects for singletons and anonymous classes.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro