skip to content

Extension Functions & Properties

Extension functions and properties let you add members to types you do not own, including nullable and generic receivers. The catch that interviews always reach — extensions resolve statically — is what separates comfortable users from confident ones.

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

explore

questions

30

In Kotlin, when you call an extension function, what determines which function actually runs: the variable's declared type or the actual object stored in it?

level: juniorimportance: must knowfreq 70%

answer

  1. Extensions resolve statically
  2. Declared type, not runtime object
  3. Members = virtual dispatch
  4. Compiles to static fn with receiver param
  5. No vtable entry for extensions

basics

~10 s

The variable's declared type decides. Kotlin picks the extension function at compile time based on how the variable is typed, not on the real object it holds at runtime.

solid answer

~40 s

Extension functions are resolved statically. The compiler chooses which extension to call from the static (declared/compile-time) type of the receiver expression, not from the object's runtime class. This is the opposite of how regular open member methods behave, which use virtual dispatch and look at the actual runtime object. So if you store a `Dog` in a variable typed as `Animal`, an extension defined on `Animal` will run even though the object is really a `Dog`. Extensions don't modify the class and aren't part of its vtable; they compile down to plain static functions that take the receiver as a hidden first parameter. Practical rule: extension calls follow the type you wrote, member calls follow the object you have.

code

kotlin · 12 lines
kotlin
open class Animal
class Dog : Animal()

fun Animal.speak() = "generic noise"
fun Dog.speak() = "woof"

fun main() {
    val pet: Animal = Dog()
    println(pet.speak()) // "generic noise" — chosen by declared type Animal
    val dog: Dog = Dog()
    println(dog.speak()) // "woof" — declared type is Dog
}

go deeper

for a junior

Knows the call follows the declared type and can read the simple Dog/Animal example correctly.

for a middle

Explains it compiles to a static function and contrasts with virtual member dispatch.

for a senior

Articulates why (no vtable entry), ties it to design risks, and predicts the result under type inference.

for a principal

Frames it as a deliberate language design tradeoff and warns teams about subtype surprises in shared APIs.

## The core idea Kotlin **extension functions** let you call a function with dot syntax (`obj.foo()`) on a type you don't own, without editing that type. But they do **not** add anything to the class. The compiler turns an extension into an ordinary static (top-level) function whose first hidden parameter is the **receiver**. ## Static vs dynamic resolution - **Static (compile-time) resolution**: the compiler decides which function to call by looking at the **declared type** of the expression you wrote — the type you can see in the source. - **Dynamic (virtual) dispatch**: at runtime the JVM looks at the **actual class** of the object and calls the most-derived override. **Member functions** use virtual dispatch (when `open`/overridden). **Extension functions** use static resolution. ```kotlin open class Animal class Dog : Animal() fun Animal.speak() = "generic noise" fun Dog.speak() = "woof" val pet: Animal = Dog() println(pet.speak()) // prints "generic noise" ``` Even though `pet` holds a `Dog`, the **declared type** is `Animal`, so `Animal.speak()` is chosen at compile time. ## Why this happens The extension above compiles roughly to: ```kotlin fun speak(receiver: Animal): String = "generic noise" ``` A static call like `speak(pet)` is bound using the static type of `pet`. There is no vtable entry, no runtime lookup. ## The key contrast | | Member function (open) | Extension function | |---|---|---| | Resolved | At runtime | At compile time | | Based on | Actual object class | Declared receiver type | | Overridable | Yes | No (only shadowed) | Remember the terms: **receiver** = the object before the dot; **declared/static type** = the type written in source; **runtime type** = the real class. Extensions follow the declared type.

  • If you change `val pet: Animal = Dog()` to `val pet = Dog()`, what does `pet.speak()` print and why?
    "woof". Type inference makes the declared type `Dog`, so the compiler statically selects `Dog.speak()`.

An extension is like a label you read off the box (the declared type), not what's actually inside the box (the runtime object).

saying these in an interview costs you the question

  • Saying extensions use polymorphism/virtual dispatch like overridden methods
  • Claiming the runtime object decides which extension runs
  • Believing extensions are added to the class or vtable
  • Confusing extensions with overriding

context

open as a page

What is an extension function in Kotlin, and how do you declare one?

level: juniorimportance: must knowfreq 85%

basics

~20 s

An extension function lets you add a new function to an existing type without changing its source. You write the type name before the function name, like fun String.shout(), and call it as if it were a normal method.

open as a page

What is a Kotlin extension property, and how do you declare one (e.g. a `lastIndex` for `String`)?

level: juniorimportance: must knowfreq 60%

basics

~10 s

An extension property lets you add a property to an existing type without changing its code. You write it like val String.lastIndex with a custom getter that computes the value.

open as a page

You declared an extension function in a different file/package than where you want to call it. How do you make it usable, and why is this different from member functions?

level: juniorimportance: must knowfreq 70%

basics

~20 s

An extension defined elsewhere is only visible if you import it by name. Add an import line for that function, then call it like normal. Member functions come for free with the object's type, so they need no import.

open as a page

In Kotlin, if a class has a member function and you also write an extension function with the exact same name and parameter signature, which one gets called when you invoke it on an instance of that class?

level: juniorimportance: must knowfreq 70%

basics

~10 s

The member function always wins. When the name and parameters match, Kotlin calls the function defined inside the class and ignores your extension. The extension is effectively shadowed.

open as a page

What does it mean for an extension function to have a nullable receiver type like `fun String?.orEmpty()`, and why can you call it on a `null` value without a NullPointerException?

level: juniorimportance: must knowfreq 70%

basics

~20 s

The function is declared on a type that allows null, so you can call it even when the value is null. Inside, you check for null yourself. No crash, because no method is dispatched on the object.

open as a page

Contrast how Kotlin resolves an overridden open member function versus an extension function declared on the same hierarchy. Show what changes when only one of them is an extension.

level: middleimportance: must knowfreq 60%

basics

~10 s

Overridden members run based on the real object (virtual dispatch). Extensions run based on the variable's declared type (static). So members can be polymorphic; extensions cannot.

open as a page

Extensions don't modify the class, so how does Kotlin compile an extension function under the hood?

level: middleimportance: must knowfreq 70%

basics

~10 s

Kotlin compiles an extension into a plain static method. The object you call it on is just passed as the first hidden argument. So a.foo() really becomes Foo.foo(a) at the bytecode level.

open as a page

Why does an extension property have no backing field, and what does the `field` keyword do (or fail to do) inside one?

level: middleimportance: must knowfreq 50%

basics

~10 s

A backing field is hidden storage attached to an instance. Extensions don't own the class, so they can't add storage. The value must be calculated each time in the getter; you can't use field.

open as a page

How does a Java caller invoke a Kotlin extension function like `fun String.shout(): String`, and how are the receiver and any parameters passed?

level: middleimportance: must knowfreq 60%

basics

~10 s

From Java, the extension is a static method on the file's facade class. You pass the receiver as the first argument: instead of 'hi'.shout() you write StringUtilsKt.shout("hi"). Extra parameters follow the receiver.

open as a page

How are the scope functions `also` and `apply` declared as generic-receiver extensions, and how do their receiver/return shapes differ?

level: middleimportance: must knowfreq 65%

basics

~20 s

Both work on any type because they take a generic receiver T. also passes the object as it and returns it; apply makes the object the lambda's this and also returns it. Both return the original object.

open as a page

Inside an extension function body, what does `this` refer to, and how do you access the receiver explicitly?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Inside the body, this is the object the function was called on (the receiver). You can write this to refer to it, or just call its members directly without this, like in a normal method.

open as a page

A teammate writes a `fun List<T>.secondOrNull()` extension and is surprised that calling it through a `Collection<T>`-typed reference doesn't compile, while a member would. Explain the underlying resolution rule and why static typing causes these surprises with supertypes.

level: middleimportance: should knowfreq 45%

basics

~10 s

Extensions are picked by the declared type. A List extension isn't visible on a Collection-typed variable, because the compiler only sees Collection. Members would be inherited and resolved on the object instead.

open as a page

When should you reach for an extension function versus a member function or a utility class, and what are the design trade-offs?

level: middleimportance: should knowfreq 50%

basics

~20 s

Use an extension when you want a method-like helper on a type you don't own or want to keep the class small, and the helper only needs the type's public API. Use a member when it needs private state or should be overridable.

open as a page

Show a correct `var` extension property with a working setter. What can its `set(value)` legally do, given there's no backing field?

level: middleimportance: should knowfreq 30%

basics

~10 s

A mutable extension property's setter must change something that already exists on the receiver — like calling a method that mutates it. It can't store the value itself because there's no field.

open as a page

What is the generated 'facade' class for top-level Kotlin functions, and how does @file:JvmName change it?

level: middleimportance: should knowfreq 55%

basics

~10 s

Top-level Kotlin functions and extensions compile into a hidden class named after the file (e.g. Utils.kt becomes UtilsKt). @file:JvmName lets you rename that class so Java code calling it sees a nicer name.

open as a page

Show how a member function and an extension function with the SAME name but DIFFERENT signatures can coexist. Walk through how overload resolution decides which to call.

level: middleimportance: should knowfreq 50%

basics

~10 s

If the parameters differ, the member and the extension are just two overloads. Kotlin picks by matching your arguments to the parameter lists, so both can be reachable depending on what you pass.

open as a page

Why does Kotlin give member functions priority over same-signature extension functions? Tie your answer to how extensions are dispatched.

level: middleimportance: should knowfreq 55%

basics

~20 s

Extensions are resolved by the compiler from the static type, not by the object at runtime. Letting them beat members would let any import silently change a class's behavior, so the member is kept authoritative.

open as a page

Inside `fun <T> T?.ifNull(default: T): T`, how do you correctly handle `this` being null, and what role do smart casts play?

level: middleimportance: should knowfreq 55%

basics

~10 s

Inside, this may be null, so test it. After if (this != null) the compiler narrows the type to non-null, letting you use it safely. Or just use Elvis: return this ?: default.

open as a page

Inside a class member that is also an extension (a function with both a dispatch receiver and an extension receiver), which receiver is dispatched virtually and which is resolved statically? What surprises arise?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The class instance (dispatch receiver) is polymorphic and chosen at runtime. The extension receiver (the type after the dot) is fixed at compile time. So overriding can change the object's behavior but not which extension target is picked.

open as a page

How do visibility modifiers apply to extension functions — what can a private/internal/member extension see and be seen by?

level: seniorimportance: should knowfreq 45%

basics

~20 s

An extension follows normal visibility rules for where it is declared, not the type it extends. A top-level public extension is usable anywhere; a private one only in its file. Because it sits outside the class, it can only see that type's public members.

open as a page

How does an extension property desugar on the JVM? What does a top-level `val String.lastIndex get() = length - 1` compile to?

level: seniorimportance: should knowfreq 35%

basics

~20 s

On the JVM it becomes a plain static method (a getter, and a setter for var) that takes the receiver as its first argument. There is no field; calling the property just calls that static method.

open as a page

When and why would you use @JvmName on an individual extension function (not the whole file), and what interop problem does it solve?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Generic types erase at runtime, so two Kotlin extensions that look distinct can compile to the same JVM signature and clash. Putting @JvmName on one of them gives it a different method name so both can coexist.

open as a page

Do the same precedence rules apply to extension PROPERTIES versus member properties? What happens if you declare an extension property whose name matches an existing member property?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Yes — the same rule holds. A member property always wins over a same-named extension property. The extension property is shadowed and never read or written through the instance.

open as a page

Compare designing an extension with a nullable receiver `fun T?.foo()` versus a non-null receiver called via `?.foo()`. What are the semantic and API-design differences?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A nullable-receiver extension runs even when the value is null and decides what null means. With a non-null receiver and ?., the whole call is skipped when null and the result is null. They behave differently for null inputs.

open as a page

When does a generic-receiver extension need `reified`, and how would you write `fun <reified T> Any?.castOrNull(): T?`-style helpers correctly?

level: seniorimportance: should knowfreq 40%

basics

~20 s

You need reified only when the function must know the real type at runtime — to do is T, as? T, or T::class. The function must be inline. Then you can write helpers that safely cast or filter by type.

open as a page

When should you reach for an extension property versus an extension function or a real member property? What are the design trade-offs?

level: principalimportance: should knowfreq 25%

basics

~20 s

Use an extension property for a cheap, no-argument, value-like accessor that reads naturally as a noun. Use an extension function when it takes arguments or does real work. Use a real member when you own the class and need stored state.

open as a page

A teammate marks a top-level extension `internal` and expects to call it from Java, and separately wants several files' extensions under one Java class. Walk through what actually happens and how to do it correctly.

level: seniorimportance: nice to knowfreq 20%

basics

~10 s

Internal extensions get a mangled, hard-to-call name from Java, so don't expect clean access. To group several files' top-level functions under one Java class, give each file the same @file:JvmName plus @file:JvmMultifileClass.

open as a page

You're designing a public library API. Argue when exposing behavior as an extension (statically resolved) is the right call versus an open member (virtually dispatched), and what binary/source compatibility and override risks each choice carries.

level: principalimportance: nice to knowfreq 18%

basics

~10 s

Use extensions for convenience helpers that shouldn't be overridden or pollute the type, and where uniform per-declared-type behavior is fine. Use open members when subtypes must customize behavior polymorphically.

open as a page

You maintain a public library. A consumer added an extension function `User.fullName()` years ago. Now you want to add a member `fun fullName()` to `User`. What happens to the consumer's code, and how do you reason about the compatibility impact?

level: principalimportance: nice to knowfreq 22%

basics

~10 s

Once your member exists, calls of the same signature start hitting the member instead of the consumer's extension. The code still compiles, but its behavior may change because the member now wins.

open as a page