Kotlin classes are final by default, meaning they cannot be inherited unless explicitly marked open. An open class can be extended, and its members can be overridden with the override keyword — which is itself open unless combined with final, allowing subclasses to further override. Abstract classes go further, forbidding instantiation and supporting abstract members that subclasses must fill in. Sealed classes take the restriction in a different direction: they limit direct subclasses to the same file (or package, in newer Kotlin versions), letting the compiler verify exhaustiveness in when expressions so you can omit the else branch safely. Unlike enum classes, where each value is a single stateless instance, sealed class subclasses can each carry their own properties and instantiate freely — making them well-suited for result hierarchies or variant types with attached data.
Several class variants offer specialized semantics. A data class — declared with the data keyword — automatically generates equals(), hashCode(), toString(), copy(), and componentN() functions. The generated copy() lets you derive a modified version of an instance, supporting functional patterns with immutable records: val older = user.copy(age = 31). The componentN() functions enable destructuring declarations like val (name, age) = User("Alice", 30), and they work the same way in for loops over maps, where for ((k, v) in map) destructures each entry. Companion objects, declared with companion object { } inside a class, hold static-style members accessible via the class name and can implement interfaces, providing a clean factory method pattern. A nested class is Java's static nested equivalent with no reference to the outer instance, while an inner class (declared with the inner keyword) keeps an implicit reference accessible as this@Outer. For a single shared instance, an object declaration creates a lazily initialized, thread-safe singleton; an object expression, by contrast, produces an anonymous class instance immediately for one-off use.
Construction in Kotlin comes in two flavors. The primary constructor is declared in the class header (class User(val name: String, val age: Int)) and can carry default values, visibility modifiers, and annotations, but contains no body. For arbitrary logic during construction, you use init { } blocks, which run in declaration order alongside property initializers; each class can have multiple init blocks, all sharing the primary constructor's parameter scope. Secondary constructors declared with the constructor keyword inside the body must delegate to the primary via this(...), and they run after the init chain completes. The overall construction order is property initializers and init blocks first (in declaration order), followed by secondary constructor bodies.
Functions and properties can be customized without inheritance. Extension functions add new functions to existing classes without modifying their sources, called with normal dot syntax; however, they are resolved statically and cannot access the receiver's private or protected members. Interfaces in Kotlin can declare abstract methods, abstract properties, and default implementations, and a single class may implement several — replacing the trait-style composition of multiple behaviors. Type aliases provide an alternative name for an existing type, which is particularly useful for simplifying complex generics or function signatures. To call an overridden member from a subclass, use super.method(), or super<InterfaceName>.method() to disambiguate when multiple parents provide the same method. Finally, several annotations smooth Java interop: @JvmStatic generates real static methods on objects and companions; @JvmField exposes a property as a public field; @JvmOverloads emits overloads for default arguments; @JvmName renames JVM methods to avoid clashes; @Throws re-enables Java-style checked-exception declarations; and @SerialName together with the @Serializable annotation integrates with kotlinx.serialization for JSON or other formats.