# How to Use a Kotlin Lambda Expression: Syntax, Use Cases, and Compiler Internals

> Master Kotlin lambda expressions with this guide. Learn syntax, practical use cases, and how the compiler handles them. Pass functions as values and simplify your code.

- Repository: [JetBrains/kotlin](https://github.com/jetbrains/kotlin)
- Tags: deep-dive
- Published: 2026-02-16

---

**A kotlin lambda expression is an anonymous function that you pass as a value to higher-order functions, store in variables, or use for SAM conversions, with the compiler representing it as `KtLambdaExpression` in the PSI and `FirAnonymousFunction` in the FIR.**

Kotlin lambda expressions are the backbone of functional programming in the language, enabling concise syntax for passing behavior as data. According to the JetBrains/kotlin source code, the compiler tracks these expressions through multiple intermediate representations, from the initial parse tree to JVM bytecode. Understanding when and how to use a kotlin lambda expression helps you write idiomatic, performant code that leverages higher-order functions and DSLs.

## What Is a Kotlin Lambda Expression?

A **kotlin lambda expression** is an anonymous function—literally a function without a name—that can be treated as a value. You can assign it to variables, pass it as arguments, or return it from other functions.

The syntax follows the pattern:

```kotlin
{ parameters -> body }

```

For example:

```kotlin
val sum: (Int, Int) -> Int = { a, b -> a + b }

```

In the JetBrains/kotlin compiler, lambdas are distinguished from anonymous functions by the `isLambda` flag in [`FirAnonymousFunction.kt`](https://github.com/JetBrains/kotlin/blob/main/FirAnonymousFunction.kt) at [`compiler/fir/tree/gen/org/jetbrains/kotlin/fir/declarations/FirAnonymousFunction.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/declarations/FirAnonymousFunction.kt). While both use the same FIR node, lambdas use the shorthand syntax without the `fun` keyword.

## Compiler Representation and Internals

The kotlin compiler processes lambda expressions through several layers, each with distinct responsibilities.

### PSI Layer: KtLambdaExpression

At the parsing stage, the compiler creates a `KtLambdaExpression` PSI element defined in [`compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtLambdaExpression.java`](https://github.com/JetBrains/kotlin/blob/main/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtLambdaExpression.java). This node wraps a `KtFunctionLiteral` that contains the parameter list, optional return type, and body block.

### FIR Layer: FirAnonymousFunction

During semantic analysis, the PSI converts to a `FirAnonymousFunction` in the FIR (Frontend Intermediate Representation). As noted in [`compiler/fir/tree/gen/org/jetbrains/kotlin/fir/declarations/FirAnonymousFunction.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/declarations/FirAnonymousFunction.kt), this node handles both lambdas and anonymous functions, using the `isLambda` boolean to differentiate them. The `typeRef` property holds the inferred functional type derived from the call site context.

### IR and Bytecode Generation

In the backend, the compiler generates a synthetic `IrFunction` for the lambda body. The compilation strategy depends on context:

- **Inlining**: When the lambda passes to an `inline` higher-order function, the compiler substitutes the lambda body directly at the call site, eliminating object allocation and enabling non-local returns.
- **Invokedynamic**: With the `-Xindy-allow-annotated-lambdas` compiler flag (defined in [`compiler/arguments/src/org/jetbrains/kotlin/arguments/description/JvmCompilerArguments.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/arguments/src/org/jetbrains/kotlin/arguments/description/JvmCompilerArguments.kt)), the compiler can emit `invokedynamic` instructions for annotated lambdas, improving JVM interoperability.
- **SAM Conversion**: When passing a lambda where a single-abstract-method (SAM) interface is expected, the compiler generates an adapter class or uses `invokedynamic` to implement the interface, forwarding calls to the lambda.

Note that lambdas compiled with `invokedynamic` are not serializable, which affects Gradle configuration cache usage as documented in [`gradle-build-conventions/gradle-plugins-common/src/main/kotlin/gradle/GradleCommon.kt`](https://github.com/JetBrains/kotlin/blob/main/gradle-build-conventions/gradle-plugins-common/src/main/kotlin/gradle/GradleCommon.kt).

## When to Use a Kotlin Lambda Expression

Use a kotlin lambda expression whenever you need to treat behavior as data. Specific scenarios include:

- **Higher-order functions**: Passing logic to functions like `map`, `filter`, or custom utilities.
- **Collection transformations**: Concise inline operations without boilerplate method definitions.
- **DSLs and builders**: Lambda with receiver syntax (`StringBuilder.() -> Unit`) creates type-safe builders like `buildString` or HTML DSLs.
- **Asynchronous programming**: Coroutine builders like `launch` and `async` accept suspending lambdas that capture surrounding scope.
- **Event listeners**: SAM conversion lets you replace verbose anonymous classes with concise lambdas for Android or JavaFX callbacks.
- **Performance-critical paths**: Combine with `inline` functions to eliminate allocation overhead and enable non-local returns.

## How to Write Kotlin Lambda Expressions

### Basic Syntax

Declare a lambda by enclosing parameters and body in braces:

```kotlin
val sum: (Int, Int) -> Int = { a, b -> a + b }

```

If the compiler can infer the type, omit the explicit signature:

```kotlin
val sum = { a: Int, b: Int -> a + b }

```

### Trailing Lambda Syntax

When a lambda is the last argument to a function, move it outside the parentheses:

```kotlin
listOf(1, 2, 3).map { it * 2 }

```

If it’s the only argument, omit the parentheses entirely:

```kotlin
run { println("Executing") }

```

### Lambda with Receiver

Use the syntax `ReceiverType.() -> ReturnType` to create DSLs where the lambda body executes in the context of a receiver object:

```kotlin
buildString {
    append("Hello, ")
    appendLine("World!")
}

```

Here, `append` and `appendLine` resolve against the implicit `StringBuilder` receiver.

### SAM Conversion

Pass a lambda where a Java single-abstract-method interface is expected:

```kotlin
val runnable = Runnable { println("Running") }
button.setOnClickListener { view ->
    view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
}

```

The compiler generates the adapter class automatically, or uses `invokedynamic` when enabled.

### Inline Higher-Order Functions

Mark functions that accept lambdas as `inline` to eliminate runtime overhead:

```kotlin
inline fun <T> measureTime(block: () -> T): T {
    val start = System.nanoTime()
    val result = block()
    println("Took ${System.nanoTime() - start} ns")
    return result
}

val result = measureTime {
    // expensive computation
}

```

Inlining enables non-local returns (`return` from the enclosing function) and avoids allocating the lambda object.

## Common Pitfalls and Diagnostics

The compiler provides specific diagnostics to help you avoid mistakes with lambdas. In [`compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java`](https://github.com/JetBrains/kotlin/blob/main/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java), the `UNUSED_LAMBDA_EXPRESSION` warning flags lambdas that are created but never invoked, often indicating a logic error where you forgot to call the function or assign the result.

Other issues to watch for:

- **Variable capture**: Lambdas capture final or effectively final variables from the enclosing scope. Modifying captured variables requires wrapping them in a mutable holder or using `AtomicReference`.
- **Return ambiguity**: Inside a lambda, `return` returns from the lambda itself (if labeled) or from the enclosing function (if used in an inline higher-order function). Unlabeled returns in non-inline lambdas are prohibited.

## Summary

- A **kotlin lambda expression** is an anonymous function treated as a value, represented in the compiler by `KtLambdaExpression` (PSI) and `FirAnonymousFunction` (FIR).
- Use lambdas for **higher-order functions**, **collection transformations**, **DSLs**, **coroutines**, and **SAM conversions** with Java interfaces.
- Apply **trailing-lambda syntax** to improve readability when the lambda is the final argument.
- Use **lambda with receiver** (`T.() -> R`) to create type-safe builders and DSLs.
- Mark higher-order functions as **inline** to eliminate allocation overhead and enable non-local returns.
- Be aware of **SAM conversion** mechanics and the `-Xindy-allow-annotated-lambdas` flag for `invokedynamic` generation, noting that such lambdas are not serializable.

## Frequently Asked Questions

### What is the difference between a lambda expression and an anonymous function in Kotlin?

Both are function literals without names, but lambdas use the concise syntax `{ params -> body }` while anonymous functions use the `fun` keyword: `fun(params): ReturnType { body }`. In the compiler’s FIR representation ([`FirAnonymousFunction.kt`](https://github.com/JetBrains/kotlin/blob/main/FirAnonymousFunction.kt)), the `isLambda` flag distinguishes them—lambdas infer return types automatically and cannot specify explicit return types, whereas anonymous functions can.

### When should I mark a higher-order function as inline with lambda parameters?

Use `inline` when the lambda is used frequently in performance-critical code, when you need **non-local returns** (returning from the enclosing function from within the lambda), or when you want to avoid the memory allocation of a function object. The compiler substitutes the lambda body directly at the call site, as seen in the IR generation phase where synthetic functions are inlined rather than invoked.

### How does SAM conversion work with Kotlin lambdas?

When you pass a lambda where a Java single-abstract-method (SAM) interface is expected, the compiler either generates a synthetic adapter class that implements the interface and forwards to your lambda, or uses an `invokedynamic` instruction if the `-Xindy-allow-annotated-lambdas` flag is enabled. This allows you to write `{ println("done") }` instead of verbose `object : Runnable { override fun run() {...} }` syntax.

### Can Kotlin lambdas be serialized?

Standard lambdas compiled to anonymous classes can be serialized if they implement `Serializable`, but lambdas compiled using `invokedynamic` (enabled via compiler flags like `-Xindy-allow-annotated-lambdas`) are not serializable. As noted in [`gradle-build-conventions/gradle-plugins-common/src/main/kotlin/gradle/GradleCommon.kt`](https://github.com/JetBrains/kotlin/blob/main/gradle-build-conventions/gradle-plugins-common/src/main/kotlin/gradle/GradleCommon.kt), this distinction is critical for Gradle configuration cache compatibility and distributed computing frameworks that require serialized closures.