Share behavior across types. Kotlin classes are final by default — you opt into inheritance with open, define contracts with abstract classes and interfaces, and control access with visibility modifiers.
Unlike Java, Kotlin classes cannot be subclassed unless you mark them open, and methods cannot be overridden unless they are open too. This "closed by default" stance prevents fragile inheritance hierarchies you did not intend. A subclass extends its parent with a colon and calls the parent constructor, overriding open members with the override keyword.
open class Animal(val name: String) { // 'open' -> can be subclassed
open fun speak() = "..." // 'open' -> can be overridden
}
class Dog(name: String) : Animal(name) { // extend + call parent constructor
override fun speak() = "Woof" // 'override' is required
}
fun main() {
val a: Animal = Dog("Rex")
println("${a.name}: ${a.speak()}") // Rex: Woof (polymorphism)
}
// closed-by-default: no accidental subclassing. Opt in with 'open'.An abstract class cannot be instantiated and can declare abstract members that subclasses must implement, alongside concrete shared behavior. Use one when several types share implementation and state but each must fill in specific pieces. It sits between a plain open class and an interface — it can hold state, which interfaces (mostly) cannot.
abstract class Shape {
abstract fun area(): Double // no body — subclasses MUST implement
fun describe() = "This shape has area ${area()}" // shared concrete behavior
}
class Circle(val r: Double) : Shape() {
override fun area() = Math.PI * r * r
}
fun main() {
// val s = Shape() // ERROR: can't instantiate an abstract class
println(Circle(2.0).describe()) // This shape has area 12.56...
}
// abstract = shared code + state + required-to-implement members.An interface defines a contract — method and property signatures — that any class can implement, and a class can implement many interfaces (unlike single class inheritance). Kotlin interfaces can provide default method implementations, so they carry behavior, not just signatures. Prefer interfaces for capabilities ("can be clicked," "can be drawn") that cut across unrelated types.
interface Clickable {
fun onClick() // required
fun showTooltip() = println("tooltip") // DEFAULT implementation — optional to override
}
interface Focusable {
fun onFocus()
}
// a class can implement MANY interfaces
class Button : Clickable, Focusable {
override fun onClick() = println("clicked")
override fun onFocus() = println("focused")
}
// interfaces = capabilities across unrelated types; multiple per class.Kotlin controls what is accessible with four visibility modifiers: public (the default, everywhere), private (the file or class), protected (the class and subclasses), and internal (the whole module). Defaulting to public but tightening to private/internal for implementation details is how you keep an API small and prevent other code from depending on internals.
Visibility modifiers (default is public):
public everywhere (the default — you rarely write it)
private visible only in the same file (top-level) or same class (member)
protected visible in the class + its subclasses (not top-level)
internal visible everywhere in the SAME MODULE (great for library internals)
class BankAccount {
private var balance = 0.0 // hidden from everyone outside
internal fun audit() { } // usable across the module, not by consumers
fun deposit(x: Double) { balance += x } // public API
}
// expose the minimum; make implementation details private/internal.