When Is the Kotlin Init Block Executed Relative to the Primary Constructor?

The init block executes immediately after primary constructor parameters are evaluated, interleaved with property initializers in textual order, and before any constructor body statements.

Understanding exactly when the kotlin init block executed is crucial for avoiding initialization bugs and side-effect ordering issues. According to the JetBrains/kotlin source code, the compiler treats init blocks as part of the primary constructor execution path, merging them with property initializers during the lowering phase. This article explains the precise execution order using actual compiler implementation details from the Kotlin repository.

Kotlin Init Block Execution Order

The kotlin init block executed follows a strict three-phase sequence that is guaranteed by the language specification and enforced by the compiler's IR lowering passes.

Step 1: Primary Constructor Parameter Evaluation

First, all arguments supplied to the primary constructor are evaluated and assigned to the constructor parameters. This happens before any class body code runs, ensuring that init blocks and property initializers can safely reference constructor parameters.

Step 2: Property Initializers and Init Blocks in Textual Order

After parameters are ready, the compiler executes all property initializers and init blocks in the exact textual order they appear in the class body. This interleaving is a critical detail: an init block runs immediately after the property initializers that precede it in the source file, and before any that follow.

This merging logic is implemented in compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt, which combines anonymous initializers (the IR representation of init blocks) with field initializers into a single ordered list that is then injected into the constructor.

Step 3: Primary Constructor Body

Finally, if the primary constructor declaration includes a body (statements between curly braces after the parameter list), those statements execute after all property initializers and init blocks have completed.

Compiler Implementation Details

The precise timing of when the kotlin init block executed is enforced by several key components in the Kotlin compiler pipeline.

JVM Backend: JvmInitializersLowering.kt

For JVM targets, the compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInitializersLowering.kt file handles the transformation. It merges field initializers and init blocks into the generated <init> method while preserving the textual order defined in the source code. This ensures that the bytecode reflects the exact interleaving required by the language specification.

Common IR Lowering: InitializersLowering.kt

The platform-agnostic logic resides in compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt. This pass runs before target-specific code generation and constructs the ordered sequence of initialization statements. By handling this at the IR level, the compiler guarantees consistent init block behavior across JVM, JavaScript, and Native targets.

PSI Representation: KtAnonymousInitializer.kt

At the front-end level, init blocks are represented by compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtAnonymousInitializer.kt. This PSI (Program Structure Interface) element captures the block's content and position within the class body, providing the textual order information that later lowering passes rely on.

Control Flow Graph: ControlFlowGraphBuilder.kt

The FIR (Front-end IR) analysis phase uses compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt to model initialization order. It treats each init block as a distinct CFG node positioned between the class entry node and the constructor body, formally verifying that the block executes after parameter setup but before constructor completion.

Code Examples Demonstrating Init Block Timing

Example 1: Textual Ordering of Initializers

The following example demonstrates how the kotlin init block executed is interleaved with property initializers based on source position.

class Demo(val a: Int) {
    val x = a * 2          // property initializer (runs after `a` is ready)
    init {
        println("init 1, a=$a, x=$x")
    }
    val y = a + 10         // second property initializer
    init {
        println("init 2, y=$y")
    }
}

Runtime output:


init 1, a=5, x=10
init 2, y=15

Explanation:

  1. Argument 5 is evaluated for a.
  2. x initializer runs (x = 5 * 2).
  3. First init block runs.
  4. y initializer runs (y = 5 + 10).
  5. Second init block runs.

Example 2: Secondary Constructor Delegation

When using secondary constructors, the init block runs during the delegated primary constructor call.

class Person(val name: String) {
    init { println("Primary init: $name") }

    constructor(name: String, age: Int) : this(name) {
        println("Secondary ctor body, age=$age")
    }
}

Runtime when Person("Bob", 30) is created:


Primary init: Bob
Secondary ctor body, age=30

The secondary constructor first delegates to the primary one via : this(name), which triggers the init block before the secondary constructor's own body executes.

Example 3: Custom Getters and Init Blocks

Properties with custom getters do not affect initialization timing because the getter executes only upon property access, not during construction.

class Counter(start: Int) {
    var count = start          // initializer runs after `start` is evaluated
    init { count++ }           // init block runs next
    val isPositive get() = count > 0   // getter evaluated only when accessed
}

Creating Counter(0) produces no output but leaves count == 1 because the init block increments the already-initialized property. The isPositive getter remains dormant until explicitly called.

Summary

  • The kotlin init block executed after primary constructor parameters are evaluated but before the constructor body completes.
  • Initialization follows strict textual order: property initializers and init blocks are interleaved exactly as they appear in the source file.
  • The compiler enforces this order through IR lowering passes in InitializersLowering.kt (common) and JvmInitializersLowering.kt (JVM), which merge initializers into the constructor bytecode.
  • Secondary constructors trigger init blocks during delegation to the primary constructor, ensuring consistent initialization regardless of which constructor is invoked.

Frequently Asked Questions

Does the init block run before or after the primary constructor?

The init block runs during the primary constructor execution, specifically after parameter evaluation but before any explicit constructor body statements. It is not separate from the constructor; rather, it constitutes part of the constructor's initialization phase as implemented in InitializersLowering.kt.

What happens if I have multiple init blocks in a Kotlin class?

Multiple init blocks execute in the order they appear in the source code, interleaved with property initializers. The compiler treats each block as a separate anonymous initializer node in the IR, then concatenates them with field initializers according to their textual position, ensuring deterministic, sequential execution.

Is the init block executed when using a secondary constructor?

Yes. When a secondary constructor delegates to the primary constructor (via : this(...)), the primary constructor's initialization sequence—including all init blocks—runs before the secondary constructor's body executes. If the secondary constructor delegates to another secondary constructor, the chain eventually reaches the primary, triggering the init blocks at that point.

Can I access all primary constructor parameters inside an init block?

Yes. Primary constructor parameters are fully available inside init blocks because parameter evaluation completes before the initialization sequence begins. You can read constructor parameters, use them to initialize other properties, and call methods on them, though the compiler will warn if you attempt to access properties that are declared after the init block in the source file before they are initialized.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →