How do default parameters work for constructors specifically, and what is DefaultConstructorMarker? Why does Kotlin use a dedicated marker type for constructors rather than a plain int-only bridge?
answer
- Constructors can't be named => synthetic <init>, not <init>$default
- Extra params: int mask + DefaultConstructorMarker
- Marker = unique type tag, prevents signature collision
- Marker always passed as null
- @JvmOverloads gives Java clean constructors
basics
~20 sFor a constructor with defaults, Kotlin generates an extra synthetic constructor that takes the params, an int bitmask, and a DefaultConstructorMarker argument. The marker gives this synthetic constructor a unique signature so it can't clash with a real one.
solid answer
~40 sConstructors can't be named, so Kotlin can't generate a 'name$default' helper the way it does for functions. Instead it emits a second, synthetic constructor whose parameter list is the original parameters plus an int bitmask plus a trailing kotlin.jvm.internal.DefaultConstructorMarker. The marker's job is purely to make this synthetic constructor's signature distinct from any constructor a user could legitimately declare, since two constructors differing only by an int could realistically collide with user code. At runtime the synthetic constructor reads the bitmask, fills in defaulted parameters, and delegates (via this(...)) to the primary constructor. Generated call sites pass null for the marker. Reflection and Java see this synthetic constructor but should not call it directly; @JvmOverloads on the constructor generates clean telescoping constructors for Java instead.
code
kotlin · 6 linesclass User @JvmOverloads constructor(
val name: String,
val role: String = "guest"
)
// Java sees: User(String) and User(String, String)
// Internally also: synthetic User(String, String, int, DefaultConstructorMarker)go deeper
Knows constructors with defaults also need @JvmOverloads for Java.
Knows a synthetic constructor with a mask and a marker is generated.
Explains precisely why constructors use DefaultConstructorMarker (no name to suffix, collision avoidance) and that it's always null.
Reasons about ABI/reflection implications of synthetic constructors and library evolution when adding defaulted constructor parameters.
## The constructor problem Functions get a helper named `f$default`. **Constructors have no name** — you can't write `<init>$default`. So Kotlin's solution is different: it emits a **second `<init>` (synthetic constructor)** with an augmented parameter list. ## What the synthetic constructor looks like For: ```kotlin class User(val name: String, val role: String = "guest") ``` Kotlin emits: 1. The **primary constructor** `User(String, String)`. 2. A **synthetic constructor** `User(String name, String role, int mask, DefaultConstructorMarker marker)`. The synthetic one reads `mask`, substitutes `"guest"` for `role` if its bit is set, then delegates to the primary constructor. ## What is DefaultConstructorMarker? `kotlin.jvm.internal.DefaultConstructorMarker` is an **empty internal class** that exists only as a **type tag**. Its sole purpose is to give the synthetic constructor a parameter slot of a type **no normal user code would ever use**, guaranteeing the synthetic signature is unique. ## Why a dedicated type instead of just an int? For *functions*, the helper has a distinct **name** (`f$default`), so a trailing `int` mask plus an `Object` slot is enough. For *constructors* there is no distinguishing name — only the parameter list separates overloads. A synthetic constructor that differed from a user constructor by only an `int` could **realistically collide** with a constructor the user actually wrote (e.g. `User(String, String, int)`). Using a type **`DefaultConstructorMarker`** that user code never references makes accidental collision effectively impossible. Generated call sites always pass **`null`** for it. ```kotlin class User(val name: String, val role: String = "guest") // new User("Ada") -> invokes synthetic User("Ada", null, mask=0b1, marker=null) // which fills role="guest" and delegates to User(String,String) ``` ## Interop and the fix Java and reflection **see** the synthetic constructor but must not call it directly (the marker/mask are internal contract). To give Java clean constructors, annotate with **`@JvmOverloads`**: ```kotlin class User @JvmOverloads constructor(val name: String, val role: String = "guest") // Generates User(String) and User(String, String) for Java. ``` ## Key terms - **Synthetic constructor**: compiler-generated `<init>` flagged synthetic. - **`DefaultConstructorMarker`**: empty internal type-tag class ensuring signature uniqueness. - **Bitmask `int`**: same omitted-argument encoding as for functions.
- Why can't constructors reuse the exact 'name$default' scheme functions use?Constructors have no name to suffix; only the parameter list distinguishes them, so a dedicated marker type is needed to keep the synthetic signature unique.
- Is DefaultConstructorMarker part of the public Kotlin API?No. It lives in kotlin.jvm.internal and is an implementation detail; you should never reference or pass it manually.
The marker is like a uniquely-shaped key tooth added so the synthetic constructor's lock can never be opened by an ordinary key the user might cut.
saying these in an interview costs you the question
- Saying constructors generate a name$default helper
- Treating DefaultConstructorMarker as a public/usable API
- Claiming the marker carries data
- Not knowing the marker prevents signature collision
- Forgetting @JvmOverloads also works on constructors