skip to content

Variables

How Kotlin binds names to values: read-only versus reassignable, when the type can be inferred, constants resolved at compile time, deferred initialization, and destructuring. Most style debates and a fair number of runtime bugs trace back to choices made in this group.

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

explore

questions

26

What is `const val` in Kotlin, and how does it differ from a regular `val`?

level: juniorimportance: must knowfreq 70%

answer

  1. const = compile-time inlined literal
  2. String + primitives only
  3. top-level / object / companion only
  4. val = runtime, getter-backed
  5. needed for annotation arguments

basics

~20 s

const val is a value the compiler knows at compile time and copies directly into the code that uses it. A plain val is set when the program runs and is read through a hidden getter method.

solid answer

~40 s

`const val` declares a compile-time constant: its value is known by the compiler and inlined directly at every use site, so no field read or getter call happens at runtime. It is restricted to `String` and primitive types (`Int`, `Long`, `Double`, `Boolean`, `Char`, etc.) and the initializer must be a constant expression. It can only live at the top level, in an `object`, or in a `companion object` — never on a local variable or inside a regular class body. A plain `val` is a read-only property: it is initialized at runtime and accessed via a synthesized getter, can be any type, and may even be backed by custom getter logic. So `const` is about compile-time inlining and a fixed primitive/String value; `val` is about runtime immutability of a reference.

code

kotlin · 11 lines
kotlin
const val MAX_RETRIES = 3          // compile-time, inlined as 3
val runtimeMax = readConfig()      // runtime value, getter-backed

object Config {
    const val BASE_URL = "https://api.example.com"  // OK
}

class Service {
    // const val X = 1   // ERROR: not allowed in a regular class body
    val x = 1            // OK: plain read-only property
}

go deeper

for a junior

Knows const val is a fixed value known at compile time, limited to String/primitives, vs a val set at runtime.

for a middle

Explains inlining at use sites, the getter-backed nature of plain val, and the scope restrictions (top-level/object/companion).

for a senior

Connects const to constant-expression requirements (annotations, when), and discusses why custom getters and arbitrary types are disallowed.

for a principal

Raises the binary-compatibility hazard of inlined constants across separately-compiled modules and weighs const vs val for public API surfaces.

## What `const val` means `const val` declares a **compile-time constant**: a value the Kotlin compiler fully resolves while compiling, then **inlines** (copies the literal) into every place the constant is referenced. There is no runtime field access and no getter call. ```kotlin const val MAX_RETRIES = 3 fun attempt() { repeat(MAX_RETRIES) { /* ... */ } // compiler emits repeat(3) } ``` At the bytecode level the literal `3` is substituted at the call site, exactly as if you had written `repeat(3)`. ## How a plain `val` differs A `val` is a **read-only property**, not necessarily a constant: - It is initialized **at runtime** (when the object/class loads or is constructed). - It is accessed through a **synthesized getter** (e.g. `getMaxRetries()`), so each read is a method call (the JIT may inline it, but the source semantics are a getter). - It can hold **any type** (objects, collections, nullable types). - It can have a **custom getter**, so its value may be computed each access. ```kotlin val maxRetries = computeFromConfig() // runtime value, getter read val timestamp get() = System.currentTimeMillis() // recomputed each access ``` ## Restrictions on `const` `const` is allowed only when ALL of these hold: - The type is `String` or a **primitive** (`Int`, `Long`, `Short`, `Byte`, `Double`, `Float`, `Boolean`, `Char`). Not `null`, not arrays, not other objects, not enum values. - The initializer is a **constant expression**: literals and operations on other `const` values are fine; function calls and runtime values are not. - It is declared at the **top level**, in an `object`, or in a `companion object`. It cannot be a local variable, a member of a regular class, or have a custom getter. - It cannot be backed by a delegate (`by`). ```kotlin const val GREETING = "Hello" const val NAME_LEN = GREETING.length // ERROR: function/property call, not a constant expression const val SCALED = 60 * 60 // OK: constant expression on literals ``` ## Why it matters - **Annotations:** annotation arguments must be compile-time constants, so `const val` (not plain `val`) can be used, e.g. `@RequestMapping(PATH)`. - **`when` branches / `switch`-like contexts** and other places requiring constant expressions accept `const val`. - **No runtime overhead** for the read, and the value is embedded even for callers compiled separately. ## Gotcha: binary compatibility Because the value is **inlined into callers**, changing a published `const val` and recompiling only the library will NOT update callers that were compiled against the old value until **they** are recompiled. A plain `val` reads the current field, so a recompiled library updates everyone.

  • Why can't you put `const val` on a property inside a normal (non-companion) class?
    A regular class member is tied to an instance and initialized at construction (runtime), so it can't be a compile-time constant. `const` requires a single, statically-known value, which is why it's limited to top-level, `object`, or `companion object` scopes.
  • Does a plain `val` guarantee the value never changes?
    It guarantees you can't reassign the reference, but a `val` with a custom getter can return different values each call, and a `val` pointing to a mutable object (e.g. a `MutableList`) can still have its contents change.

const val is like a value printed directly onto every page (copied everywhere); a plain val is like a phone number you look up in a directory each time.

saying these in an interview costs you the question

  • Claiming `const val` works on any type or on objects/collections
  • Saying `const` can be declared as a local variable
  • Thinking `const val` and `val` are identical at the bytecode level
  • Believing a plain `val` is always a compile-time constant
  • Not knowing `const` requires top-level/object/companion scope

context

open as a page

What is a destructuring declaration in Kotlin, and what does `val (name, age) = person` actually do under the hood?

level: juniorimportance: must knowfreq 70%

basics

~10 s

It unpacks one object into several variables in a single line. Each variable is filled by calling the object's component functions in order: the first gets component1(), the second component2().

open as a page

What is `lateinit var` in Kotlin and what problem does it solve?

level: juniorimportance: must knowfreq 80%

basics

~20 s

lateinit var lets you declare a non-null variable without giving it a value right away. You promise to set it before you read it. It's used when a value isn't known at object creation but will be ready before first use.

open as a page

What is type inference in Kotlin, and how does the compiler decide the type of `val n = 1`?

level: juniorimportance: must knowfreq 80%

basics

~20 s

Type inference lets you skip writing the type. The compiler reads the value on the right and figures out the type itself. For val n = 1 it sees a whole number, so n is an Int.

open as a page

What is the difference between val and var in Kotlin, and when should you reach for each?

level: juniorimportance: must knowfreq 92%

basics

~10 s

val means you set the value once and cannot reassign it. var means you can reassign it later. Prefer val by default and use var only when the value really needs to change.

open as a page

What types and scopes does `const val` allow, and what must its initializer look like?

level: middleimportance: must knowfreq 60%

basics

~20 s

const val only works for text (String) and basic number/boolean/char types. You can declare it at the top of a file, in an object, or in a companion object. Its value must be something the compiler can figure out without running code.

open as a page

What are the exact restrictions on where `lateinit` can be used, and how do you defer initialization of a primitive like `Int`?

level: middleimportance: must knowfreq 60%

basics

~20 s

lateinit only works on a mutable var with a non-null, non-primitive type and no custom getter/setter. For a primitive like Int, you can't use lateinit; instead use by Delegates.notNull<Int>(), which throws if read before being set.

open as a page

When does Kotlin REQUIRE you to write an explicit type instead of relying on inference?

level: middleimportance: must knowfreq 70%

basics

~10 s

When there's no value to infer from. If a variable or property has no initializer yet, or the compiler can't work out the type on its own, you must write the type explicitly.

open as a page

Explain why `val` does not guarantee immutability. How do you actually express an immutable value in Kotlin?

level: middleimportance: must knowfreq 78%

basics

~20 s

val only stops you from pointing the name at a different object. The object it points to can still change if it is mutable. For real immutability you also need an immutable type, like List instead of MutableList.

open as a page

Why must annotation arguments (and certain `when`/`switch`-like contexts) use `const val` rather than a plain `val`?

level: middleimportance: should knowfreq 40%

basics

~20 s

Annotations are baked into the compiled code, so their values must be known at compile time. A plain val is only set when the program runs, so it can't be used there. const val is known at compile time, so it works.

open as a page

How does `for ((key, value) in map)` work, and what enables destructuring a `Map.Entry` and lists/arrays?

level: middleimportance: should knowfreq 60%

basics

~10 s

Iterating a map yields Map.Entry objects, and each entry can be unpacked into key and value because Map.Entry provides component1() (key) and component2() (value). Lists and arrays provide component1()..component5() too.

open as a page

How do you skip components you don't need in a destructuring declaration, and what does the underscore actually do?

level: middleimportance: should knowfreq 55%

basics

~10 s

Use an underscore in the position you want to ignore. It tells the compiler to skip that component so the corresponding componentN() is never called and no variable is created.

open as a page

What exactly happens when you read a `lateinit var` before assigning it, and how does this differ from a nullable property?

level: middleimportance: should knowfreq 55%

basics

~20 s

Reading a lateinit var before it's set throws an UninitializedPropertyAccessException with a message naming the property. A nullable property instead returns null, which you can handle with ?. or ?: — it never throws on read.

open as a page

How does `::prop.isInitialized` work, and from where can you call it?

level: middleimportance: should knowfreq 50%

basics

~10 s

::prop.isInitialized checks whether a lateinit property has been assigned yet, returning true or false without throwing. You usually call it from inside the same class using this::prop.isInitialized to avoid an exception before first use.

open as a page

Why is `val x = 1` inferred as `Int` but you might need `val x: Long = 1`? Explain integer literal types and how to force `Long`.

level: middleimportance: should knowfreq 55%

basics

~20 s

A plain number like 1 is treated as an Int by default. If you need a Long, either write the type (val x: Long = 1) or add an L suffix (1L). The value is the same; the type differs.

open as a page

What are the initialization rules for val? Can a val be assigned later, conditionally, or remain uninitialized?

level: middleimportance: should knowfreq 55%

basics

~20 s

A val must be set exactly once before it is read. You can split the declaration and the assignment, and even assign it in different branches of an if, as long as every path sets it once and you never read it before then.

open as a page

A library exposes a public `const val VERSION = "1.0"`. The maintainer bumps it to `"1.1"` and republishes only the library JAR. Why might consumers still see `"1.0"`, and how does this differ from a plain `val`?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Because const values get copied directly into the code that uses them at compile time. Consumers that were already compiled still carry the old copied-in value until they are recompiled. A plain val is read fresh from the library each run, so updating the library is enough.

open as a page

How do you make your own non-data class destructurable, and why might you deliberately add a `componentN()` operator?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Declare operator fun componentN() functions on the class — one per position you want to expose. They can return anything, including computed values, not just constructor properties.

open as a page

What are the design risks of `lateinit`, and how would you decide whether to use it in a class API?

level: seniorimportance: should knowfreq 40%

basics

~20 s

lateinit lets objects exist in a half-built state, so calling methods in the wrong order crashes at runtime instead of compile time. Use it only when an external system controls timing (DI, lifecycle); otherwise prefer constructor injection or lazy so the value is guaranteed present.

open as a page

Compare `lateinit var` with `by lazy`. When would you choose each?

level: seniorimportance: should knowfreq 65%

basics

~20 s

lateinit var is a mutable value you assign yourself from outside, later. by lazy is an immutable val that computes itself automatically the first time it's read. Use lateinit when something else supplies the value; use lazy when the object can compute it on demand.

open as a page

How does Kotlin infer the type of an expression like `if`/`when` branches or `listOf(1, "a")`, and what subtle pitfalls (e.g. widening to `Any`, platform types, generic inference) should you watch for?

level: seniorimportance: should knowfreq 45%

basics

~10 s

When values have different types, Kotlin picks the nearest common parent type. Mixing an Int and a String gives Any. Watch out: the inferred type can be wider than you want, hiding bugs.

open as a page

How does return-type inference work for functions and properties, and when should you annotate the return type even though the compiler could infer it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

A function written with = (an expression body) can have its return type guessed from the result. Block functions with { } cannot. Even when inference works, write the return type for public functions so callers aren't surprised by refactors.

open as a page

How do val and var differ for class properties (getters/setters), and why can a var property block a smart cast?

level: seniorimportance: should knowfreq 64%

basics

~20 s

A val property only gets a getter; a var property gets a getter and a setter. A var (especially a public or open one) can change between a null check and its use, so the compiler refuses to smart-cast it because the value might no longer be non-null.

open as a page

Where do `const val` declarations end up in JVM bytecode for top-level, `object`, and `companion object` cases, and how does that affect Java interop and `@JvmStatic`?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

A const val becomes a fixed static final field in the compiled class. Top-level ones live on the file's generated class; companion ones live on the outer class. Java can read them directly by name, and you don't need @JvmStatic.

open as a page

How does destructuring work in lambda parameters, and what are the gotchas around the single-vs-multiple-parameter distinction?

level: seniorimportance: nice to knowfreq 30%

basics

~10 s

A lambda parameter wrapped in parentheses is destructured into its components instead of received whole. { (k, v) -> ... } takes ONE entry parameter and unpacks it, not two separate parameters.

open as a page

How do val and var compile on the JVM, and what does that mean for Java interop and the meaning of 'final'?

level: seniorimportance: nice to knowfreq 38%

basics

~20 s

A val property becomes a Java getter method, and a var adds a setter. A local val compiles to a final local variable. val controls reassignment in Kotlin, but Java code calling the getter just sees a normal method, so val is not a deep immutability promise across the boundary.

open as a page