An exported Kotlin class has two functions named `add` — one taking Int, one taking String. What goes wrong when exported to JS, and how do you produce a clean .d.ts?
answer
- JS: one name = one function (no overloading)
- Overloaded export → clash diagnostic
- Fix: @JsName distinct names, or collapse, or keep one public
- Default args synthesize overloads → hidden ambiguity
- Design exported surface JS-shaped
basics
~20 sJavaScript has no method overloading, so two functions with the same name clash. The compiler complains. You rename each with @JsName, or merge them into one function, so the generated TypeScript file is clean and unambiguous.
solid answer
~50 sKotlin allows overloading; JavaScript does not — one property name maps to one function. When you `@JsExport` two members sharing a name, the compiler reports an overload-clash diagnostic because they'd collide on the same JS property and the generated `.d.ts` couldn't represent two distinct signatures cleanly. Fixes: (1) give each a distinct JS identity with `@JsName("addInt")` / `@JsName("addString")`; (2) collapse them into a single function whose parameter is an exportable union-friendly type (often a `dynamic`/`Any?` at the edge, then internal `when`), accepting weaker typing; or (3) redesign so only one overload is part of the public surface and keep the others internal/non-exported. The `.d.ts` then exposes either two clearly named methods or one. Also remember default arguments don't translate — Kotlin synthesizes overloads for defaults, which can itself create export ambiguity, so prefer explicit, single-signature exported APIs.
code
kotlin · 9 lines@JsExport
class Calc {
@JsName("addInt")
fun add(x: Int): Int = x + 1
@JsName("addString")
fun add(x: String): String = "$x!"
}
// .d.ts: addInt(x: number): number; addString(x: string): stringgo deeper
Recognizes that two same-named functions are a problem in JS.
Names @JsName as the fix and can write distinct JS names for each overload.
Weighs the three fixes, knows default arguments synthesize overloads, and designs a JS-shaped exported surface.
Sets an API-design policy (one-name-one-function, explicit params, no defaults across the boundary) and reasons about long-term .d.ts stability for consumers.
## Root cause: JS has no overloading In JavaScript an object has at most **one** property called `add`. Kotlin lets you write: ```kotlin @JsExport class Calc { fun add(x: Int): Int = x + 1 fun add(x: String): String = x + "!" // overload } ``` When exported, both want to become `Calc.prototype.add`. They **collide**, and the Kotlin/JS compiler raises an **overload-clash** diagnostic for the exported surface because it cannot emit two functions under one JS name, and the generated TypeScript declaration (`.d.ts`) can't faithfully describe the pair. ## Fix 1 — @JsName to give distinct JS names ```kotlin @JsExport class Calc { @JsName("addInt") fun add(x: Int): Int = x + 1 @JsName("addString") fun add(x: String): String = x + "!" } ``` Generated `.d.ts`: ```ts export class Calc { addInt(x: number): number addString(x: string): string } ``` Clean and unambiguous. The Kotlin call sites still use `add(...)`. ## Fix 2 — collapse to one function Provide a single exported entry point and branch internally: ```kotlin @JsExport class Calc { @JsName("add") fun addAny(x: dynamic): dynamic = when (x) { is Int -> x + 1 is String -> "$x!" else -> error("unsupported") } } ``` This matches JS ergonomics but **loses static typing** (`dynamic` becomes `any` in TS). Use sparingly. ## Fix 3 — keep only one overload public Mark the extra overloads non-exported (don't annotate them / keep them `internal`) and expose only the canonical one. Often the cleanest for a real public API. ## Watch out: default arguments Kotlin compiles default arguments by synthesizing extra overloads under the hood. Across `@JsExport`, that can introduce hidden overload ambiguity or surprising `.d.ts` output. Prefer **explicit, single-signature** exported functions; if you need optional params for JS, model them as nullable parameters or a small options class. ```kotlin @JsExport fun connect(host: String, port: Int, useTls: Boolean): Unit { /* ... */ } // rather than relying on defaults across the boundary ``` ## Principle Design the **exported surface** to be JS-shaped: one name = one function, explicit parameters, exportable types. Use `@JsName` as the precise tool to project Kotlin overloads onto distinct JS identities.
- Why are Kotlin default arguments risky on an exported function?Defaults are implemented by compiler-synthesized overloads, which can introduce overload ambiguity or unexpected entries in the generated .d.ts across the export boundary.
- What is the typing cost of collapsing overloads into one dynamic-typed function?The parameter degrades to `any` in TypeScript, so consumers lose compile-time type checking on that argument.
Overloading in JS is like two people insisting on the same phone number — you must give each a distinct number (@JsName) or route everyone through one switchboard (a single function).
saying these in an interview costs you the question
- Claiming JS supports method overloading
- Thinking the compiler silently picks one overload to export
- Ignoring that default arguments synthesize overloads
- Using dynamic everywhere instead of @JsName, killing TS types
- Not knowing @JsName resolves the clash while keeping Kotlin call sites unchanged