skip to content

Contracts

Contracts let a function tell the compiler things it cannot infer — that a check implies a type, or that a lambda runs exactly once. They are why stdlib functions enable smart casts that your own helpers do not.

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

explore

questions

15

Why can you assign to a val exactly once inside a `run { }` lambda and have the compiler accept it as definitely initialized afterwards? What language feature makes this work?

level: juniorimportance: must knowfreq 55%

answer

  1. run/let/with carry EXACTLY_ONCE contracts
  2. callsInPlace = invoked in place + how many times
  3. enables definite-assignment of val in lambda
  4. without contract: lambda assumed 0..many times
  5. stdlib declares it; consumer needs no opt-in

basics

~20 s

The standard-library functions like run and let promise the compiler that the lambda you pass runs exactly once. Because of that promise, the compiler knows a val you set inside the lambda gets set once, so it treats it as initialized.

solid answer

~40 s

Scope functions such as `run`, `let`, `with`, and `apply` carry a Kotlin *contract* declared with the `contracts` block and `callsInPlace(block, InvocationKind.EXACTLY_ONCE)`. This tells the compiler the lambda parameter is invoked in place, synchronously, exactly once before the function returns. With that guarantee the compiler's definite-assignment analysis can prove a `val` written once inside the lambda is assigned exactly once — so it is initialized after the call and can be referenced, and the `val` cannot be re-assigned. Without the contract the compiler would see the lambda as possibly never called (or called many times) and reject `val` assignment inside it, forcing you to use a different pattern. The contract is in the stdlib source, so you get this for free using these functions.

code

kotlin · 6 lines
kotlin
val name: String
run {
    val raw = readLine().orEmpty()
    name = raw.trim()   // assigning a val from inside the lambda — allowed
}
println(name)            // definitely initialized here

go deeper

for a junior

Knows that you can set a val inside run/let and use it afterwards, and that it's something special these functions provide.

for a middle

Names the contract and EXACTLY_ONCE, and explains definite-assignment analysis as the mechanism.

for a senior

Can reproduce the stdlib contract declaration and explain why a custom function without it fails; distinguishes 'in place' from invocation count.

for a principal

Frames it as how compile-time effects extend the type system's flow analysis, and discusses the experimental status and stability trade-offs of authoring such contracts.

## The problem A `val` (read-only local) in Kotlin must be assigned **exactly once** along every path. The compiler runs *definite-assignment analysis* to prove this. When you pass a lambda to a normal higher-order function, the compiler cannot know whether or how many times that lambda runs, so it conservatively assumes it might run **zero or many** times. Writing a `val` inside such a lambda would then look like "maybe never assigned" or "maybe assigned twice" — both illegal. ```kotlin fun myOwn(block: () -> Unit) { block() } val x: Int // without a contract, this fails myOwn { x = 42 } // error: captured val cannot be assigned / not initialized println(x) // error: variable might not be initialized ``` ## The fix: callsInPlace Kotlin **contracts** let a function describe its behavior to the compiler. The relevant effect is `callsInPlace`: ```kotlin import kotlin.contracts.InvocationKind import kotlin.contracts.contract @OptIn(ExperimentalContracts::class) inline fun <R> myRun(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } ``` `callsInPlace(block, kind)` promises two things: - **In place**: the lambda is invoked *synchronously*, before the enclosing function returns — not stored, not run later on another thread. - **Kind**: how many times — here `EXACTLY_ONCE`. With `EXACTLY_ONCE`, the compiler now reasons: the lambda runs once, so a `val` assigned inside runs once. Definite-assignment passes: ```kotlin val x: Int myRun { x = 42 } // OK: assigned exactly once println(x) // OK: definitely initialized ``` ## The real stdlib functions The standard scope functions already declare this. Their source contains, for example: ```kotlin public inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } ``` So `run`, `let`, `with`, `also`, `apply`, and `repeat` (which uses `AT_LEAST_ONCE`) all benefit from it. That's why this pattern works without you writing any contract yourself. ## Key terms - **Contract**: compile-time-only metadata describing a function's effect; defined in a `contract { }` block as the first statement of the body. - **Definite assignment**: the compiler proof that a `val`/`var` is set before use. - **InvocationKind**: enum — `EXACTLY_ONCE`, `AT_MOST_ONCE`, `AT_LEAST_ONCE`, `UNKNOWN`. Note contracts are still marked experimental (`@ExperimentalContracts`) for *authoring*, but consuming the stdlib's contracts requires no opt-in.

  • Would the same val assignment compile if you wrote your own `fun apply2(block: () -> Unit)` without a contract?
    No. Without `callsInPlace`, the compiler assumes the lambda may run zero or many times, so it rejects the val assignment and treats the variable as possibly uninitialized.
  • Does this require the function to be `inline`?
    The stdlib functions are inline, but `callsInPlace` itself works for non-inline functions too; it just describes invocation, independent of inlining.

It's a signed promise note: the function swears 'I'll call your lambda exactly once, right now' so the compiler trusts a one-time val assignment inside it.

saying these in an interview costs you the question

  • Says it works 'because run is inline' (inlining is unrelated to the assignment proof)
  • Thinks any higher-order function allows val assignment inside its lambda
  • Confuses this with smart casts / `returns` effects
  • Believes the lambda result is what makes it work, not the contract
  • Claims you must add @ExperimentalContracts to just call run

context

open as a page

After calling require(x is String) or x?.let { ... } in stdlib, the compiler often lets you treat x as the narrowed type without an extra cast. What language feature makes the standard library functions like require, check, and requireNotNull propagate type information to the caller?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Kotlin contracts. The stdlib functions declare a promise about how they behave, so the compiler knows that if the call returns normally, a condition is true. It then smart-casts the variable for you.

open as a page

What is the contract { } block in Kotlin, and why would you add one to a function?

level: juniorimportance: should knowfreq 35%

basics

~20 s

A contract is a small block at the start of a function that tells the compiler extra facts about how the function behaves, so it can do smarter checks like smart casts after you call your own function.

open as a page

Write a custom inline function that wraps a block and lets callers initialize a `val` from inside its lambda. What is the exact `contract { }` declaration, what opt-in is needed, and what placement rules apply?

level: middleimportance: should knowfreq 35%

basics

~10 s

Put a contract as the very first statement of the function and call callsInPlace(block, EXACTLY_ONCE). Mark it with the experimental contracts opt-in. Then callers can set a val inside the lambda.

open as a page

Explain the four `InvocationKind` values used with `callsInPlace` and what each one permits the compiler to assume about a `val` (and a `var`) assigned inside the lambda.

level: middleimportance: should knowfreq 45%

basics

~20 s

The kind says how often the lambda runs: exactly once, at most once (zero or one), at least once (one or more), or unknown. Only 'exactly once' lets you assign a val and use it afterward as initialized.

open as a page

What syntactic and structural rules constrain where and how a contract { } block can be declared?

level: middleimportance: should knowfreq 30%

basics

~10 s

The contract block must be the first line inside the function, it can only go on regular named functions with a body, and you must opt in to the experimental API.

open as a page

Which Kotlin standard library functions rely on contracts, and what observable behavior do those contracts unlock for callers?

level: middleimportance: should knowfreq 28%

basics

~10 s

Functions like require, check, requireNotNull, isNullOrEmpty, and the scope functions (let, run, also, apply, with, repeat) declare contracts so smart casts and val initialization work cleanly after you call them.

open as a page

Explain the difference between returns(), returns(true), returns(false), and returnsNotNull() in a Kotlin contract. Give a stdlib or realistic example where each is the right choice.

level: middleimportance: should knowfreq 45%

basics

~10 s

They describe which return outcomes guarantee a condition. returns() means 'any normal return'; returns(true)/returns(false) tie the guarantee to a specific Boolean result; returnsNotNull() ties it to returning a non-null value.

open as a page

Write a custom function with a returns-effect contract so callers get a smart cast, and list the rules/limitations the contract block must obey (placement, opt-in, what conditions are allowed).

level: middleimportance: should knowfreq 38%

basics

~20 s

Put a contract { } block as the very first statement, opt in with the experimental annotation, and use returns(...) implies (param is Type / param != null). Then callers can use the parameter as the narrowed type after the call.

open as a page

A teammate adds `callsInPlace(block, InvocationKind.EXACTLY_ONCE)` to a function that actually stores `block` in a field and invokes it later on a background thread. Why is this dangerous, and what guarantees does `callsInPlace` really make?

level: seniorimportance: should knowfreq 28%

basics

~20 s

callsInPlace promises the lambda runs right now, synchronously, inside the call — not stored or run later. Lying about that lets the compiler assume a val is safely set when it really isn't, causing crashes or uninitialized reads.

open as a page

Contracts are unchecked. What goes wrong if a contract { } block lies, and how would you reason about that risk when shipping a library?

level: seniorimportance: should knowfreq 22%

basics

~20 s

The compiler believes whatever the contract says without checking it. If the contract is false, the compiler makes wrong smart casts, so code that looks safe can throw at runtime. You must make sure the body truly honors the contract.

open as a page

Kotlin contracts are unverified by the compiler. Explain the soundness risk of returns effects, when smart casts driven by a contract can silently break, and how this constrains where you should declare them.

level: seniorimportance: should knowfreq 28%

basics

~20 s

The compiler believes your contract without checking the body. If the body doesn't actually enforce the promised condition, the compiler smart-casts based on a false promise, which can throw at runtime. So contracts must be written carefully and kept in sync with the body.

open as a page

Which standard-library functions rely on `callsInPlace` contracts, what invocation kind does each use, and how does that affect what you can write inside their lambdas?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Scope functions like run, let, with, also, apply use 'exactly once', so you can set a val inside them. repeat uses 'at least once', so a val can't be set there but a var can.

open as a page

Walk through the opt-in mechanics for the contracts DSL: what makes it experimental, how do you opt in at function vs module level, and what does that imply for a published API?

level: seniorimportance: nice to knowfreq 16%

basics

~20 s

The contract { } function is marked experimental, so the compiler warns or errors unless you opt in. You can opt in on one function with @OptIn, or for the whole module with a compiler flag. Either way you accept that the API could change.

open as a page

Compare contract-based narrowing (requireNotNull, isNullOrEmpty) with alternatives like the Elvis operator, !!, and plain if-checks for null/type narrowing. When does the contract approach earn its keep, and what does it cost?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Contracts let helper functions carry the narrowing so callers stay clean and get a real smart cast plus a clear exception. Elvis and if-checks narrow inline without any helper; !! narrows but throws a vague NPE. Contracts cost an experimental opt-in and unverified trust.

open as a page