Describe the exact bytecode the Kotlin compiler emits for a top-level function with default parameters. Name the synthetic method, its extra parameters, and what each does.
answer
- Real method + synthetic name$default
- int mask: bit set = argument omitted
- >32 params => multiple int masks
- Trailing Object marker, always null
- Default expressions evaluated once, inside the bridge
basics
~20 sBesides the normal method, Kotlin emits a synthetic helper named function$default. It takes the original parameters plus an int bitmask (which args were omitted) and a trailing Object marker. It fills in defaults, then calls the real method.
solid answer
~40 sFor fun f(a: Int, b: Int = 2) the compiler emits the real f(int, int) plus a synthetic, static f$default(int a, int b, int mask, Object marker). The mask is a bitmask where bit N set means 'argument N was not supplied, use its default'. The helper checks each bit; if set, it overwrites that slot with the computed default expression, then delegates to f. The trailing Object is a marker parameter (the constructor-marker pattern, DefaultConstructorMarker for constructors) used to disambiguate the synthetic signature and reserve a slot; it is always null at call sites. Kotlin call sites that omit arguments compile to a call to f$default with the appropriate mask bits set, rather than to f directly. This keeps a single source of truth for default expressions while supporting omission.
code
kotlin · 4 linesfun f(a: Int, b: Int = 2, c: String = "x") {}
// Emits: f(int,int,String)
// and: synthetic f$default(int,int,String,int mask,Object marker)
// f(1) routes to f$default with bits for b and c set.go deeper
Knows a hidden helper method exists in addition to the real one.
Names function$default, explains the int bitmask semantics and the marker parameter precisely.
Explains call-site routing, the >32-parameter multi-mask case, and where default expressions are evaluated.
Connects the encoding to binary-compatibility consequences when defaults are added or reordered in a published library.
## What gets generated Given: ```kotlin fun f(a: Int, b: Int = 2, c: String = "x") { /* ... */ } ``` the compiler emits **two** JVM methods: 1. **`f(int, int, String)`** — the real implementation with the full parameter list. 2. **`f$default(int a, int b, String c, int mask, Object marker)`** — a **synthetic** static helper. ## The bitmask parameter `mask` is an `int` used as a **bitmask**. Each default-eligible parameter is assigned a bit. **Bit N set = argument N was omitted**, so the helper should substitute that parameter's default. For example, `f("only a")` omits `b` and `c`, so the relevant bits are set and the helper computes `2` and `"x"` into those slots. With many parameters (>32) the compiler emits **multiple** `int` mask parameters. ## The trailing Object marker The last `Object` parameter is a **marker** slot. For **constructors** this is typed `DefaultConstructorMarker` and exists so the synthetic constructor signature cannot clash with a real user-declared constructor; for functions it plays the analogous disambiguation/reservation role. It is **always passed as `null`** by generated call sites — it carries no data; its presence alone distinguishes the bridge. ## How call sites route A Kotlin call that omits any default argument does **not** call `f` directly — it calls `f$default(...)` with the computed `mask`. A call that supplies **every** argument calls `f` directly. So the default **expressions exist once**, inside `f$default`. ```kotlin // f(1) -> f$default(1, 0, null, mask=0b110, marker=null) // f(1, 5) -> f$default(1, 5, null, mask=0b100, marker=null) // f(1, 5, "z") -> f(1, 5, "z") // direct, no bridge ``` ## Why this design - **Single source of truth**: the default expression is evaluated in exactly one place. - **Per-argument granularity**: the bitmask lets any subset of trailing/middle arguments be omitted (Kotlin allows omitting a middle one via named arguments). - **Binary stability tradeoff**: adding a new default parameter changes the `$default` signature and the call-site masks, which is why such changes can be **binary-incompatible** for already-compiled callers. ## Key terms - **Synthetic**: compiler-generated, flagged `ACC_SYNTHETIC`, hidden from source. - **Bitmask**: integer whose bits encode which arguments were omitted. - **`DefaultConstructorMarker`**: the marker type for synthetic default *constructors*.
- Why is there an extra Object/DefaultConstructorMarker parameter?To give the synthetic default-constructor/method signature a distinct shape that can't collide with a user-declared one; it's always null and carries no value.
- What happens with more than 32 default parameters?One int holds 32 mask bits, so the compiler emits additional int mask parameters as needed (one per 32 parameters).
saying these in an interview costs you the question
- Saying the bitmask encodes the values rather than which were omitted
- Claiming defaults are duplicated into every call site
- Forgetting the method is synthetic/static
- Not knowing the marker is always null
- Thinking one int can cover unlimited parameters