skip to content

How Kotlin Does Classes & Objects

How Kotlin reshapes OOP: classes are final and public unless you say otherwise, the primary constructor declares properties inline, object makes singletons a language feature, and by gives you delegation without boilerplate. Understanding these defaults explains most of the design differences from Java.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

In Kotlin, what are the default visibility and inheritance modifiers for a top-level class declared with just `class Foo`, and why were those defaults chosen?

level: juniorimportance: must knowfreq 78%

answer

  1. public + final by default
  2. open to allow subclassing, override to override
  3. members final too, open them individually
  4. Effective Java Item 19 — fragile base class
  5. no package-private; internal = module scope

basics

~10 s

A plain Kotlin class is public so any code can use it, and final so nobody can subclass it. To allow subclassing you must write the keyword open.

solid answer

~40 s

By default a top-level `class Foo` is `public` (visible everywhere) and `final` (cannot be subclassed or have its members overridden). This is the opposite of Java, where classes are subclassable unless marked `final`. To permit inheritance you mark the class `open`, and each method/property you want overridable must also be `open`; the overriding member uses `override`. The defaults follow Joshua Bloch's 'design and document for inheritance or else prohibit it' — final-by-default prevents fragile-base-class bugs from accidental overriding. Visibility options are `public` (default), `internal` (same Gradle module), `protected` (subclasses), and `private`. There is no package-private. `internal` replaces Java's package-private at the module granularity.

code

kotlin · 9 lines
kotlin
open class Repository {            // open: subclassable
    open fun find(id: Int) = id    // open: overridable
    fun version() = 1              // final: locked
}

class CachedRepo : Repository() {
    override fun find(id: Int) = id * 2
    // override fun version() ... // ERROR: version is final
}

go deeper

for a junior

States the two defaults (public, final) and that open enables subclassing.

for a middle

Adds member-level finality, override being implicitly open, and the visibility ladder including internal.

for a senior

Explains the fragile-base-class rationale and the all-open/kotlin-spring plugin for proxy-based frameworks.

for a principal

Frames final-by-default as an API-evolution and binary-compatibility policy, weighing it against framework needs and module-boundary design.

## The two defaults When you write `class Foo`, Kotlin applies two implicit modifiers: - **Visibility = `public`** — the class is visible to all other code that can see its declaring scope. Kotlin's visibility modifiers are: - `public` (default): visible everywhere. - `internal`: visible within the same *module* (a Gradle/Maven compilation unit). This replaces Java's package-private but at module granularity. - `protected`: visible in the declaring class and its subclasses (not available on top-level declarations). - `private`: visible in the declaring file (for top-level) or the declaring class. - There is **no package-private** in Kotlin. - **Inheritability = `final`** — the class cannot be subclassed, and its members cannot be overridden. ## Opening a class for inheritance To subclass, mark the class `open`. Members are also final by default; mark each overridable member `open` too. The subclass uses `override`. ```kotlin open class Animal { open fun speak() = "..." fun id() = 42 // final: cannot be overridden } class Dog : Animal() { override fun speak() = "Woof" } ``` ## Why final-by-default? This follows *Effective Java* Item 19: "Design and document for inheritance or else prohibit it." Subclassing that the base author didn't anticipate creates the **fragile base class problem** — internal changes silently break subclasses. Making `final` the default forces a deliberate `open` opt-in, so inheritance is always intentional and documented. ## Related keywords - `abstract` classes/members are implicitly `open` (you must override them). - `override` members are themselves `open` again unless you mark them `final override` to stop the chain. - `sealed` is a restricted form of `open` where all subclasses must be known at compile time. - The Gradle `all-open`/`kotlin-spring` compiler plugin auto-opens annotated classes (e.g. Spring `@Component`) so you don't sprinkle `open` everywhere for proxying.

  • How does Kotlin's `internal` differ from Java's package-private?
    `internal` is visible across the whole module (compilation unit), not just one package; Java's package-private is limited to a single package.
  • If a method is `open` in the base and you `override` it, can a further subclass override it again?
    Yes — an `override` member is implicitly `open`. Write `final override` to stop further overriding.

A Kotlin class is like a sealed appliance: you can use it but can't pry it open to modify internals unless the maker added a removable panel (open).

saying these in an interview costs you the question

  • Saying Kotlin classes are open/subclassable by default like Java
  • Claiming the default visibility is package-private
  • Thinking `open` on the class alone makes all members overridable
  • Believing `internal` means package-private
  • Not knowing `abstract` members are implicitly open

context

open as a page

Kotlin makes singletons a first-class language feature with `object`. Describe object declarations, companion objects, and object expressions, and how each is initialized.

level: middleimportance: must knowfreq 72%

basics

~20 s

object Foo { } declares a single, lazily created instance — a built-in singleton. A companion object is the one singleton attached to a class for 'static-like' members. An object : Type { } expression creates a one-off anonymous object on the spot.

open as a page

Explain how Kotlin's primary constructor can declare properties inline, and what `val`/`var` in the constructor header does versus a plain parameter. When do you need an `init` block or a secondary constructor?

level: middleimportance: must knowfreq 80%

basics

~10 s

Putting val or var before a primary-constructor parameter makes it a stored property of the class in one line. Without val/var it's just a constructor argument. init blocks run setup code at construction.

open as a page

Kotlin makes delegation a language feature via the `by` keyword for both interfaces and properties. Explain class/interface delegation and property delegation, and what the compiler generates.

level: seniorimportance: should knowfreq 58%

basics

~20 s

by lets one object hand off work to another automatically. For interfaces, a class can implement an interface by forwarding all its methods to a held object. For properties, getting/setting a value can be routed through a helper like lazy or Delegates.observable.

open as a page

Kotlin maps OOP onto the JVM with opinionated defaults (final classes, no statics, language-level delegation). As an API designer, what are the consequences of these choices for library evolution, Java interop, and framework integration?

level: principalimportance: nice to knowfreq 32%

basics

~20 s

Kotlin's defaults make code safer and shorter but require extra care: final classes can block frameworks that subclass your types, and 'no statics' plus companions need annotations for clean Java use. Plan these at the API boundary.

open as a page