Kotlin Language: Variables & Types Refresher
A spaced-repetition refresher on Variables & Types in Kotlin Language, focused on practical implementation details and updates.
Table of contents
Context and Scope
Run date: 2026-03-05. This is a focused refresher on Kotlin variables and core types for the “Kotlin Language → Variables & Types” track. We’ll cover exactly these 11 concepts: val, var, Type Inference, String, String Templates, Int/Long/Double/Float, Boolean, Char, Any, Unit, and Nothing. The prior post was kotlin-variables-types-new-topic-2026-03-04-2, last reviewed 2026-03-04.
Conceptual Model & Analogy
Think of memory like labeled containers on a workbench:
- val is a sealed, labeled container: you fill it once and can’t swap it out for another. You can still open what’s inside and change its contents if that object itself is mutable.
- var is a resealable container: you can replace what’s inside with something else of the same type.
- Types are the shape of containers: they determine what fits.
- Any is the ultimate “catch-all” container shape that anything can fit into.
- Unit is a label for “this operation doesn’t produce a meaningful thing.”
- Nothing is “there is no possible thing here,” used when a function never returns.
Deep Dive
- val (read-only variables)
- You assign a val exactly once. Reassignment is prohibited, but the referenced object may still mutate (for example, a MutableList). Official docs recommend defaulting to val. (kotlinlang.org)
- var (mutable variables)
- You can reassign a var any number of times after initialization. Prefer val by default and use var when mutation is essential. (kotlinlang.org)
- Type Inference (local and expression-level)
- Kotlin infers most local variable and single-expression function return types from initializers/expressions, so you can omit explicit annotations where the intent is clear. With the K2 compiler (default since Kotlin 2.0), inference and smart casts improved, reducing the need for workarounds in complex scenarios. For public API surfaces, keep explicit types for clarity and binary compatibility. (kotlinlang.org)
- String
- Kotlin’s String is immutable. You can create escaped strings (”…”) or raw, triple-quoted multiline strings ("""…"""). Raw strings preserve newlines and don’t process backslash escapes; use trimMargin/trimIndent to clean leading whitespace. (kotlinlang.org)
- String Templates
- Interpolate values with $name or ${expression}. In multiline strings you can’t backslash-escape characters; to insert a literal $ use ”${’$’}”. As of Kotlin 2.1 (Preview), multi-dollar string interpolation lets you choose how many consecutive $ characters trigger interpolation, dramatically simplifying strings that contain many literal dollar signs (enable with -Xmulti-dollar-interpolation). (kotlinlang.org)
- Int / Long / Double / Float
- Kotlin provides Int and Long for integral values; Double and Float for floating-point per IEEE 754. On the JVM, non-null numeric values are compiled to primitives for performance, and become boxed (wrapper objects) when nullable or used in generics; identity (===) on boxed numbers can surprise due to JVM caching of small values—use == for numeric equality. Kotlin does not perform implicit widening conversions; call toX() explicitly. (kotlinlang.org)
- Boolean
- Boolean holds true or false. Operators || and && short-circuit; ! negates. On the JVM, Boolean is a primitive when non-null and boxed when nullable. (kotlinlang.org)
- Char
- Char represents a single Unicode character literal in single quotes, distinct from a one-character String. Use digitToInt/digitToIntOrNull for numeric conversions; on the JVM, Char is a primitive when non-null and boxed when nullable. (kotlinlang.org)
- Any
- Any is the root supertype of all non-null Kotlin types (Any? is the top of the entire hierarchy, including null). It defines equals, hashCode, and toString. On the JVM, Any maps to Object at runtime. Prefer specific types in APIs; use Any sparingly for heterogeneous values. (kotlinlang.org)
- Unit
- The return type of functions that don’t return a meaningful value. The compiler infers Unit for block-bodied functions without an explicit return value; you usually omit it except where a function type requires it, e.g., () -> Unit. (kotlinlang.org)
- Nothing
- A bottom type with no values; a function returning Nothing never returns normally (it always throws or loops forever). This helps the compiler reason about control flow (code after a Nothing call is unreachable). The stdlib exposes Nothing as a real type across Kotlin targets. (kotlinlang.org)
Implementation Patterns
- Default to val; introduce var only when mutation is essential and localized. This makes data flow easier to reason about and reduces bugs. (kotlinlang.org)
- Prefer type inference for locals; specify explicit types for public APIs and overload-heavy code to improve readability, refactor safety, and incremental builds. (kotlinlang.org)
- Prefer String templates over concatenation; for content heavy in literal $ (templating, JSON schema, shell), enable multi-dollar interpolation and use $$… or $$$… raw strings. (kotlinlang.org)
- Choose numeric types deliberately:
- Int unless values exceed Int range; then Long.
- Double for general floating-point; Float for memory-constrained or interop-specific cases.
- Use underscores in numeric literals for readability (1_000_000). Avoid ==/=== confusion on boxed numbers. (kotlinlang.org)
- Distinguish Char vs String; prefer Char for single code units and String for text. Use Char APIs for code-point/digit work. (kotlinlang.org)
- Any is for generic holders or dynamic data ingress; narrow as soon as possible (is checks + smart casts). (kotlinlang.org)
- Model void-like operations with Unit and “never returns” paths with Nothing (e.g., fail()). This improves exhaustiveness and static reasoning. (kotlinlang.org)
Baseline Example
fun main() {
// val vs var
val pi = 3.14159 // inferred Double
var count = 0 // inferred Int
count += 1 // mutable
// Strings
val name = "Kotlin"
println("Hello, $name!") // template
// Raw multiline string (no escaping)
val banner = """
|Welcome to $name
|Version: ${2}.${1}
""".trimMargin()
println(banner)
// Numbers: explicit conversions, no implicit widening
val i: Int = 42
val d: Double = i.toDouble() // must convert
println("As double: $d")
// Boolean and Char
val isReady: Boolean = true
val letter: Char = 'A'
println("Ready? $isReady, first letter: $letter")
}
Production-grade/Advanced Example (combining several concepts)
// A tiny config loader that demonstrates: String templates (incl. multi-dollar),
// type inference, Any narrowing, Unit, Nothing, and numeric conversions.
// Enable multi-dollar interpolation in your build (Kotlin ≥ 2.1):
// kotlin { compilerOptions { freeCompilerArgs.add("-Xmulti-dollar-interpolation") } }
data class AppConfig(
val port: Int,
val debug: Boolean,
val apiKey: String
)
// A helper that never returns: useful for “fail-fast” configuration checks.
fun fail(message: String): Nothing = throw IllegalStateException(message)
// Parse a loosely-typed map (e.g., from JSON/YAML/env) using Any and narrow types early.
fun loadConfig(raw: Map<String, Any?>): AppConfig {
val port = when (val p = raw["PORT"]) {
is Int -> p
is String -> p.toIntOrNull() ?: fail("PORT must be an Int")
null -> 8080
else -> fail("Unsupported type for PORT: ${p::class.simpleName}")
}
val debug = when (val d = raw["DEBUG"]) {
is Boolean -> d
is String -> d.equals("true", ignoreCase = true)
null -> false
else -> fail("Unsupported type for DEBUG: ${d::class.simpleName}")
}
val apiKey = (raw["API_KEY"] as? String)?.takeIf { it.isNotBlank() }
?: fail("API_KEY is required and cannot be blank")
return AppConfig(port = port, debug = debug, apiKey = apiKey)
}
// Use multi-dollar string interpolation for a JSON-like template that contains many `$` signs.
fun renderDiagnostics(cfg: AppConfig): String {
// With `$$` prefix: two consecutive `$` trigger interpolation; single `$` stays literal.
return $$"""
{
"$schema": "https://example.org/schema",
"$id": "app://config",
"debug": ${cfg.debug},
"http": {
"port": ${cfg.port},
"format": "$$HTTP_$$METHOD" // literal placeholders like "$HTTP_$METHOD"
},
"meta": "Service: $${System.getProperty("user.name") ?: "unknown"}"
}
""".trimIndent()
}
fun main() {
val raw: Map<String, Any?> = mapOf( // type inference for locals; explicit in APIs
"PORT" to "9090",
"DEBUG" to "TRUE",
"API_KEY" to "sk_live_123"
)
val cfg = loadConfig(raw)
println(renderDiagnostics(cfg)) // returns a String; println is a Unit-returning function
}
- Notes:
- fail returns Nothing, so the compiler knows subsequent code is unreachable on those branches. (kotlinlang.org)
- renderDiagnostics uses multi-dollar string interpolation ($$""" … """) to keep many $ signs literal without ${’$’} noise. (kotlinlang.org)
- We narrow Any to concrete types using when + is checks; smart casts apply. (kotlinlang.org)
Common Pitfalls and Tradeoffs
- “val is immutable” confusion: val prevents reassignment of the reference, not mutation of the referenced object. For true immutability, use immutable data structures or expose read-only views. (kotlinlang.org)
- Overusing inference at API boundaries: it can obscure intent and slow incremental builds. Use explicit signatures for public APIs, but leverage inference in local scopes. (kotlinlang.org)
- String templates vs formatting: prefer templates for readability; use String.format on Kotlin/JVM only when you need advanced locale-aware formatting. (kotlinlang.org)
- Dollar signs in raw strings: before Kotlin 2.1 you had to write ”${’$’}”. In Kotlin 2.1+ (Preview), prefer multi-dollar interpolation for heavy-$ content, but remember to enable the compiler flag. (kotlinlang.org)
- Numeric boxing and ===: nullable or generic numeric values are boxed; identity equality (===) can be surprising because of JVM caches for small integers. Use == for value equality. (kotlinlang.org)
- No implicit numeric widening: explicitly convert with toX(); this avoids silent precision issues. (kotlinlang.org)
- Char vs String: a ‘1’ is a Char, not a String; convert consciously (e.g., digitToInt). (kotlinlang.org)
Technical Note
- Multi-dollar string interpolation is in Preview as of Kotlin 2.1 and requires opt-in with -Xmulti-dollar-interpolation (or Gradle compilerOptions freeCompilerArgs). This does not break existing single-dollar templates. If you ship libraries, document the flag for contributors. (Checked on 2026-03-05.) (kotlinlang.org)
- K2 compiler (default since 2.0) improves type inference and smart casts; if migrating from 1.9 or earlier, consult the K2 migration guide for subtle behavior changes (for example, better common-supertype smart casts in disjunctions). (kotlinlang.org)
- Numbers: some legacy numeric conversion functions are deprecated for certain types (for example, toByte() from Float/Double); audit older code during upgrades. (kotlinlang.org)
Sources & Further Reading
- Basic syntax: val/var, Unit, type inference (overview) — https://kotlinlang.org/docs/basic-syntax.html (kotlinlang.org)
- Strings, templates, raw strings, and multi-dollar interpolation (Updated Feb 4, 2026) — https://kotlinlang.org/docs/strings.html (kotlinlang.org)
- What’s new in Kotlin 2.1.0 (multi-dollar interpolation; enabling flags) — https://kotlinlang.org/docs/whatsnew21.html (kotlinlang.org)
- Numbers: ranges, conversions, boxing, equality (Updated Dec 14, 2025) — https://kotlinlang.org/docs/numbers.html (kotlinlang.org)
- Booleans — https://kotlinlang.org/docs/booleans.html (kotlinlang.org)
- Characters — https://kotlinlang.org/docs/characters.html (kotlinlang.org)
- Any (stdlib API) — https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-any/ (kotlinlang.org)
- Unit and function returns — https://kotlinlang.org/docs/functions.html (kotlinlang.org)
- Nothing (stdlib API) — https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-nothing/ (kotlinlang.org)
- Kotlin spec: type system (top Any?, bottom Nothing) — https://kotlinlang.org/spec/type-system.html (kotlinlang.org)
- K2 compiler migration guide (inference and smart-cast improvements) — https://kotlinlang.org/docs/k2-compiler-migration-guide.html (kotlinlang.org)
Check Your Work
Hands-on Exercise
- Task: Implement parseArgs(args: Array
): Map<String, Any?> that recognizes: - —port=NNN (Int), —debug=(true|false) (Boolean), —name=STRING (String), and ignores unknown flags.
- Use val by default and introduce var only where needed.
- Use type inference locally but give parseArgs an explicit return type.
- Build a summary banner using a raw multiline string with either ”${’$’}” or multi-dollar interpolation if you’ve enabled it.
Brain Teaser
- You have:
- val a = 128; val b: Int? = a; val c: Int? = a
- What do (b == c) and (b === c) return on the JVM, and why?
- Now set val a2 = 100; val b2: Int? = a2; val c2: Int? = a2; What changes?
- Explain your answers referencing boxing and small-integer caches, and state why you should use == for numeric comparisons. (kotlinlang.org)
References
- kotlinlang.org/docs/basic-syntax.html
- kotlinlang.org/docs/strings.html
- kotlinlang.org/docs/whatsnew21.html
- kotlinlang.org/docs/numbers.html
- kotlinlang.org/docs/booleans.html
- kotlinlang.org/docs/characters.html
- kotlinlang.org/api/core/kotlin-stdlib/kotlin/-any
- kotlinlang.org/docs/functions.html
- kotlinlang.org/api/core/kotlin-stdlib/kotlin/-nothing
- kotlinlang.org/spec/type-system.html
- kotlinlang.org/docs/k2-compiler-migration-guide.html
- kotlinlang.org/docs/booleans.html
- kotlinlang.org/docs/characters.html
- kotlinlang.org/api/core/kotlin-stdlib/kotlin/-any
- kotlinlang.org/docs/functions.html
- kotlinlang.org/api/core/kotlin-stdlib/kotlin/-nothing
- kotlinlang.org/spec/type-system.html
- kotlinlang.org/docs/k2-compiler-migration-guide.html
Share
More to explore
Keep exploring
3/27/2026
Kotlin Language: Variables & Types Refresher
A spaced-repetition refresher on Variables & Types in Kotlin Language, focused on practical implementation details and updates.
3/12/2026
Kotlin Language: Variables & Types Refresher
A spaced-repetition refresher on Variables & Types in Kotlin Language, focused on practical implementation details and updates.
3/4/2026
Kotlin Language: Variables & Types Deep Dive (Part 2/2)
A detailed learning-in-public deep dive on Variables & Types in Kotlin Language, covering concept batch 2/2.
Previous
Jetpack Compose: Core Concepts Deep Dive (Part 2/2)
Next