An abstract class cannot be instantiated directly. It may contain abstract members (without implementation) and concrete members (with bodies). Subclasses must implement all abstract members or be abstract themselves.
Abstract class exampleabstract class Animal(val name: String) {
abstract fun makeSound()
fun sleep() {
println("$name is sleeping")
}
}
class Dog(name: String) : Animal(name) {
override fun makeSound() {
println("$name says Woof!")
}
}
fun main() {
val dog = Dog("Buddy")
dog.makeSound()
dog.sleep()
}
Abstract class vs regular class
Feature
Abstract class
Regular class
Instantiation
Cannot create objects directly
Can create objects
Abstract members
Allowed (abstract fun)
Not allowed
State (properties)
Can hold mutable state
Can hold state
Inheritance
Single class inheritance only
Single class inheritance only
Abstract properties
Abstract propertyabstract class Shape {
abstract val name: String
abstract fun area(): Double
}
class Circle(val radius: Double) : Shape() {
override val name = "Circle"
override fun area() = Math.PI * radius * radius
}
When to use abstract class
Use when subclasses share common state or base behavior — e.g. a BaseFragment with shared lifecycle helpers, or a BaseViewModel with common loading state.
2Interfaces
An interface defines a contract — a set of methods and properties that implementing classes must provide. Kotlin interfaces can contain abstract members, default implementations, and properties.
Basic interfaceinterface Clickable {
fun click()
fun showPointer() {
println("Pointer visible") // default implementation
}
}
Interface properties
Property in interfaceinterface UserRepository {
val isConnected: Boolean
fun getUser(id: Int): String
}
Abstract class vs interface
Aspect
Abstract class
Interface
Inheritance count
One class only
Multiple interfaces allowed
Constructor
Has constructor parameters
No constructor
State storage
Can store state in properties
Properties usually abstract or without backing field
Default methods
Concrete methods in class body
Default implementations in interface body
Best for
Shared base + state
Capabilities, contracts, APIs
Functional interface (SAM)fun interface OnClickListener {
fun onClick()
}
fun main() {
val listener = OnClickListener { println("Clicked!") }
listener.onClick()
}
fun interface: A functional interface (Single Abstract Method) can be implemented with a lambda — common in Android callbacks and Compose event handlers.
3Interface Implementation
A class implements an interface using the same : syntax as inheritance. All abstract members must be overridden unless the class is abstract.
Implementing an interfaceinterface Drawable {
fun draw()
}
class Button(val label: String) : Drawable {
override fun draw() {
println("Drawing button: $label")
}
}
fun main() {
val btn: Drawable = Button("Submit")
btn.draw()
}
Class extending base + implementing interface
Inheritance + interfaceopen class Vehicle(val brand: String)
interface Electric {
fun charge()
}
class Tesla(brand: String) : Vehicle(brand), Electric {
override fun charge() {
println("$brand is charging...")
}
}
fun main() {
Tesla("Model 3").charge()
}
Overriding default interface methods
Override default implementationinterface Logger {
fun log(message: String) {
println("[LOG] $message")
}
}
class FileLogger : Logger {
override fun log(message: String) {
println("[FILE] $message")
}
}
fun main() {
FileLogger().log("Saved")
}
Implementing interface properties
Interface property implementationinterface Configurable {
val configName: String
}
class AppConfig : Configurable {
override val configName: String = "production"
}
Program to the interface
Declare variables as interface types — val repo: UserRepository = RemoteUserRepository() — so implementations can be swapped (e.g. for testing with a fake repository).
4Multiple Interfaces
Kotlin supports multiple interface inheritance — a class can implement several interfaces by listing them after the superclass, separated by commas. Kotlin does not support multiple class inheritance.
Multiple interfacesinterface Flyable {
fun fly()
}
interface Swimmable {
fun swim()
}
class Duck : Flyable, Swimmable {
override fun fly() = println("Duck flying")
override fun swim() = println("Duck swimming")
}
fun main() {
val duck = Duck()
duck.fly()
duck.swim()
}
Class + multiple interfaces
One class, two interfacesopen class Employee(val name: String)
interface Payable {
fun pay(amount: Double)
}
interface Reportable {
fun generateReport(): String
}
class Manager(name: String) : Employee(name), Payable, Reportable {
override fun pay(amount: Double) {
println("Paid $name: $$amount")
}
override fun generateReport(): String {
return "Report for $name"
}
}
Resolving conflicting defaults
When two interfaces provide the same default method, the class must override it and choose which implementation to use:
Conflict resolution with super<Interface>interface A {
fun greet() = println("Hello from A")
}
interface B {
fun greet() = println("Hello from B")
}
class C : A, B {
override fun greet() {
super<A>.greet()
super<B>.greet()
println("Hello from C")
}
}
fun main() {
C().greet()
}
Allowed
Not allowed
class X : Base(), I1, I2, I3
Extending two classes
Interface extending multiple interfaces
Multiple class inheritance
interface I : I1, I2
—
Android example: An Activity implements lifecycle interfaces implicitly; custom classes often implement Serializable, Parcelable, or domain interfaces like Repository alongside a base class.
5Visibility Modifiers
Visibility modifiers control who can access classes, interfaces, and their members. Kotlin's modifiers apply to declarations at class, function, and property level.
Modifier
Class member
Top-level declaration
public
Visible everywhere
Visible everywhere (default)
private
Visible inside the declaring class only
Visible in the same file only
protected
Visible in the class and subclasses
Not allowed at top level
internal
Visible within the same module
Visible within the same module
Visibility on classes and interfaces
Visibility examplespublic class PublicApi // default — visible everywhere
internal class ModuleHelper // visible only in this module
private class InternalUtil // visible only in this file
interface Repository {
fun fetch(id: Int): String // public by default
private fun validate(id: Int) { // private helper inside interface
require(id > 0)
}
}
Protected in inheritance
protected membersopen class Base {
protected open fun onInit() {
println("Base init")
}
}
class Derived : Base() {
override fun onInit() {
super.onInit()
println("Derived init")
}
fun start() = onInit() // OK — same class hierarchy
}
Visibility on interface implementations
Implementations cannot reduce visibilityinterface Service {
fun execute()
}
class MyService : Service {
// Must be public — cannot make interface method private
override fun execute() {
println("Executing...")
}
internal fun helper() { } // internal is OK for new methods
}
Module vs package
internal
Hides API within a Gradle module — ideal for implementation details in libraries.
private (top-level)
File-scoped — helpers in the same .kt file only.
protected
Exposed to subclasses but hidden from unrelated classes.
public
Default for published APIs — use intentionally for library surface.
API design
Expose interfaces as public, keep implementations internal where possible — callers depend on the contract, not the concrete class. Related: Encapsulation in OOP.
6Summary Cheatsheet
Topic
Key Takeaway
Abstract Classes
abstract class; abstract + concrete members; single inheritance
Interfaces
Contract with abstract/default members; no constructor