What is `const val` in Kotlin, and how does it differ from a regular `val`?
answer
- const = compile-time inlined literal
- String + primitives only
- top-level / object / companion only
- val = runtime, getter-backed
- needed for annotation arguments
basics
~20 sconst 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 linesconst 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
Knows const val is a fixed value known at compile time, limited to String/primitives, vs a val set at runtime.
Explains inlining at use sites, the getter-backed nature of plain val, and the scope restrictions (top-level/object/companion).
Connects const to constant-expression requirements (annotations, when), and discusses why custom getters and arbitrary types are disallowed.
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