skip to content

What is the `dynamic` type in Kotlin/JS, and how does it change the compiler's behavior compared with a normal typed value?

level: middleimportance: must knowfreq 50%

answer

  1. dynamic = type-checker OFF
  2. Kotlin/JS only — not JVM/Native
  3. Any member/call compiles; resolved at runtime
  4. Operations return dynamic (infectious)
  5. Pin back with unsafeCast<T>()

basics

~20 s

dynamic is a special Kotlin/JS type that turns off compile-time checks. You can read any property or call any method on it and the compiler won't complain — correctness is your responsibility, checked only at runtime by JavaScript.

solid answer

~40 s

`dynamic` is a type available only in Kotlin/JS (not on JVM/Native) that opts a value **out** of Kotlin's type system. Any member access, method call, or operator on a `dynamic` value compiles without checking — it's emitted verbatim as a JS member access, and resolution happens at runtime. Assigning `dynamic` to a typed variable, or a typed value to `dynamic`, is allowed implicitly. Results of operations on `dynamic` are themselves `dynamic` (it's 'infectious'). It's the escape hatch for genuinely free-form JS — JSON blobs, loosely-typed libraries, or `js("...")` results. The cost: no autocomplete, no null-safety, no signature verification; typos become runtime `TypeError`s. Idiomatic Kotlin/JS prefers `external` typed facades and confines `dynamic` to a thin boundary, casting back to real types (often via `unsafeCast`) as soon as possible.

code

kotlin · 11 lines
kotlin
fun parse(raw: dynamic) {
    // compiles no matter what — runtime decides
    val first: String = raw.users[0].name
    val n: Int = raw.count
    // narrow back to a real type ASAP
    val typed = raw.unsafeCast<List<dynamic>>()
}

// dynamic spreads:
val a: dynamic = js("42")
val b = a + 1   // b is also dynamic, not Int

go deeper

for a junior

Knows dynamic disables type checks and is JS-only.

for a middle

Explains implicit conversions, the infectious result type, and runtime-only resolution.

for a senior

Confines dynamic to a boundary, narrows with unsafeCast, and articulates the tradeoff versus external facades.

for a principal

Sets team policy on where dynamic is acceptable, how to wrap third-party JS safely, and the maintainability/error-surface implications.

## What `dynamic` is `dynamic` is a built-in type that exists **only in Kotlin/JS**. (Use it on JVM or Native and the code won't compile.) Declaring something `dynamic` tells the compiler: *stop type-checking this value entirely.* It behaves like JavaScript's untyped values. ```kotlin val obj: dynamic = js("({ name: 'Ada', age: 36 })") console.log(obj.name) // no checking — compiles console.log(obj.whatever.deep) // also compiles; fails only at runtime if wrong obj.doStuff(1, 2, 3) // any call shape is allowed ``` ## How it changes the compiler - **No member checks.** `value.foo`, `value.bar()`, `value[i]`, even operators are accepted regardless of whether they exist. The compiler emits the corresponding JS and lets the JS runtime resolve it. - **No null safety.** `dynamic` sits outside the nullable/non-null distinction; you get no `?.` enforcement. - **Implicit conversions both ways.** A typed value can flow into a `dynamic`, and a `dynamic` can be assigned to a statically typed variable **without a cast** — the compiler trusts you. - **Infectious results.** The result type of most operations on a `dynamic` is again `dynamic`, so it spreads unless you pin it back to a real type. - **Some things still apply.** `null`/comparison semantics and a few language constructs aren't fully bypassed, but for member access it's effectively 'anything goes'. ## Pinning back to typed code Because `dynamic` removes all safety, idiomatic code narrows it quickly. The stdlib offers `unsafeCast<T>()` to reinterpret a value as a concrete type with **zero runtime check** (you assert the shape): ```kotlin external interface Person { val name: String } val p: Person = obj.unsafeCast<Person>() ``` ## When to use it - Free-form JSON / config objects whose shape you don't model. - Quick interop with a loosely typed JS library before writing a proper `external` facade. - The result of `js("...")` inline JavaScript. ## `external` vs `dynamic` `external` keeps **full** type checking against signatures you declare; `dynamic` **removes** it. Prefer `external` facades; keep `dynamic` to a thin, well-commented boundary so typos don't silently become runtime `TypeError`s deep in your app.

  • Can you use `dynamic` in a Kotlin/JVM module?
    No. `dynamic` is only valid in Kotlin/JS targets; it won't compile elsewhere, which matters in multiplatform common code.
  • What is `unsafeCast` and how does it differ from a normal `as` cast?
    `unsafeCast<T>()` reinterprets a value as `T` with no runtime type check, unlike `as`, which can throw `ClassCastException`. It's used to leave `dynamic` for a real type when you're sure of the shape.

Like casting everything to any in TypeScript: the compiler stops arguing and you own every mistake.

saying these in an interview costs you the question

  • Claiming `dynamic` works in common/JVM code
  • Using `dynamic` everywhere instead of `external` facades
  • Forgetting that operations on `dynamic` yield `dynamic`
  • Expecting null-safety or autocomplete on a `dynamic`
  • Thinking the compiler validates member access on `dynamic`

context