10 min read

Kotlin Language: Variables & Types Refresher

A spaced-repetition refresher on Variables & Types in Kotlin Language, focused on practical implementation details and updates.

#Learning-log#Kotlin Refresher

Table of contents

  1. Context and Scope
  2. Conceptual Model & Analogy
  3. Deep Dive
  4. Implementation Patterns
  5. Common Pitfalls and Tradeoffs
  6. Technical Note
  7. Sources & Further Reading
  8. Check Your Work

Context and Scope

This is a focused refresher on Kotlin’s Variables & Types for the Kotlin Language map, Fundamental level, Group “Variables & Types,” concept batch 1/1. It revisits exactly 11 core concepts you must know, matching the prior refresher (kotlin-variables-types-refresher-2026-03-05). Run date: 2026-03-12. The guidance and code assume Kotlin 2.x with the K2 compiler enabled by default and current docs as of late 2025–early 2026.

Covered concepts (11/11):

  • val
  • var
  • Type Inference
  • String
  • String Templates
  • Int / Long / Double / Float
  • Boolean
  • Char
  • Any
  • Unit
  • Nothing

Conceptual Model & Analogy

Think of your program as a labeled storage wall:

  • val is a sealed jar: once you put something in, you can’t swap it for something else. You can still change the contents if they’re mutable (for example, add items to a mutable list inside the jar).
  • var is a reusable container: you can replace what’s inside as many times as you want.
  • Types are the labels on the containers: they describe what fits.
  • Any is the top shelf that can hold anything.
  • Unit is like a receipt that says “operation done, nothing meaningful to return.”
  • Nothing is a trap door: control flow falls through it and never comes back, signaling “unreachable past here.”

Deep Dive

  1. val: read-only reference
  • Declare with val when the reference won’t be reassigned; it improves reasoning and enables more compiler checks. Example: val nums = mutableListOf(1, 2) lets you mutate the list but not rebind nums. Kotlin’s basic syntax explicitly distinguishes val from var. (kotlinlang.org)
  1. var: mutable reference
  • Use var when the reference must change (counters, accumulators, state machines). Reassigning is allowed any number of times; prefer narrow scope to reduce surface for bugs. (kotlinlang.org)
  1. Type Inference
  • Kotlin deduces many types from initializers and usage, so you can omit explicit annotations for locals; the compiler infers the smallest fitting integer type starting from Int, and Double for fractional literals by default. With K2, call resolution and type inference are more consistent; add explicit types when inference becomes ambiguous at API boundaries. (kotlinlang.org)
  1. String
  • Kotlin Strings are immutable sequences of characters. Kotlin supports both escaped strings (”…”) and triple-quoted raw strings ("""…""") that can include newlines without escaping; use trimMargin/trimIndent to format. (kotlinlang.org)
  1. String Templates
  • Interpolate with $name or ${expr} in both single-line and multiline strings. Since Kotlin 2.1–2.2, “multi-dollar string interpolation” allows $$ or $$$ to control when interpolation triggers, making it easier to include literal $ in templates. (kotlinlang.org)
  1. Int / Long / Double / Float
  • Kotlin’s numeric primitives behave like classes but are represented as optimized primitives at runtime on the JVM. No implicit widening: convert explicitly (toLong, toDouble, etc.). Be mindful of boxing and number-caching on the JVM when using referential equality (===) versus structural equality (==). (kotlinlang.org)
  1. Boolean
  • Boolean holds true/false and supports short-circuiting logical operators (||, &&) and negation (!). Short-circuiting means the right-hand side may not be evaluated. (kotlinlang.org)
  1. Char
  • Char represents a single 16‑bit Unicode code unit (not a String). Convert as needed (‘A’.code, ‘A’.toString()) and remember that some user-perceived characters can span more than one Char. (kotlinlang.org)
  1. Any
  • Any is the root of the type hierarchy; every class ultimately inherits from it. Prefer more specific types or generics for APIs; use Any sparingly (e.g., logging, heterogeneous containers). (kotlinlang.org)
  1. Unit
  • Functions that return no meaningful value have return type Unit; you can omit it in declarations. In function types, () -> Unit is explicit and common for callbacks. (kotlinlang.org)
  1. Nothing
  • Nothing has no values and denotes code paths that never produce a result (throw or infinite loop). It helps the compiler understand unreachable code and narrow types after failure helpers (e.g., error, TODO, custom fail). The Kotlin spec also treats a bare null literal as Nothing? when no context is available. (kotlinlang.org)

Baseline example (tying all 11 together):

fun main() {
    // val vs var + type inference
    val greeting: String = "Hello"
    var counter = 0          // inferred Int
    counter += 1

    // Numbers: Double by default for fractional, Float with suffix
    val price = 19.99        // Double
    val pi: Float = 3.14f

    // Boolean with short-circuiting; Char vs String
    val ok: Boolean = true
    val first: Char = 'A'

    // String + raw multiline + templates
    val name = "Kotlin"
    val multiline = """
        Welcome to $name!
        First char code: ${first.code}
    """.trimIndent()
    println(multiline)

    // Any root type
    val top: Any = 42L
    when (top) {
        is Long -> println("Long: $top")
        else -> println("Other")
    }

    // Unit can be omitted on declaration; explicit in function types
    fun log(msg: String): Unit = println(msg)
    log("$greeting, $name. ok=$ok, counter=$counter, price=${price + 0.01}")

    // Nothing: helper that never returns
    fun fail(reason: String): Nothing = error(reason)
    if (!ok) fail("Not OK") // code after this branch is unreachable
}

Implementation Patterns

  • Prefer val by default; use var only when you must reassign. This aligns with functional-by-default style and reduces incidental state.
  • Be deliberate with type inference:
    • Locals: let the compiler infer.
    • Public APIs, DTOs, boundaries: annotate explicitly for readability, stability, and better error messages under K2’s stricter inference in edge cases. (kotlinlang.org)
  • Strings and templates:
    • Use raw strings (""") for JSON, SQL, or multi-line messages.
    • With Kotlin 2.2+, use multi-dollar interpolation to include $ literally in templates (e.g., ”$$” or ”$$$”). (kotlinlang.org)
  • Numbers:
    • Choose Int by default; use Long when range demands.
    • Avoid === (referential) for boxed numbers; prefer ==. (kotlinlang.org)
  • Booleans:
    • Leverage short-circuiting (||, &&) to guard expensive calls. (kotlinlang.org)
  • Char vs String:
    • Model text with String; use Char only when you truly need a single UTF‑16 code unit. (kotlinlang.org)
  • Any:
    • Avoid Any in APIs; prefer sealed hierarchies or generics unless you need open-ended polymorphism. (kotlinlang.org)
  • Unit:
    • Omit Unit in function declarations but keep () -> Unit explicit for callbacks. (kotlinlang.org)
  • Nothing:
    • Centralize non-returning helpers (fail, unreachable) to improve flow analysis and readability. (kotlinlang.org)

Production-grade example: defensive parsing + structured errors + modern string templates

// Kotlin 2.2+ for stable multi-dollar interpolation.
//
// Reads environment-like settings and builds a JSON report that includes
// keys starting with '$' without awkward escaping.

data class Settings(
    val retries: Int,          // Int vs Long: Int by default
    val endpoint: String
)

// A fail-fast helper that never returns; improves flow typing (Nothing).
private fun fail(msg: String): Nothing = throw IllegalStateException(msg)

fun parseSettings(env: Map<String, String>): Settings {
    // Prefer val; rebind only when necessary.
    val retries = env["RETRIES"]?.toIntOrNull()
        ?: 3 // sensible default

    val endpoint = env["ENDPOINT"] ?: fail("Missing ENDPOINT") // past here, endpoint is non-null String

    return Settings(retries = retries, endpoint = endpoint)
}

fun buildJsonReport(s: Settings): String {
    // Multi-dollar interpolation: "$$" means "$" is literal; "$$${...}" still interpolates.
    // Here, $$$ means only three $ in a row trigger interpolation; $ and $$ remain literal.
    val $$$json = $$$"""
    {
      "$schema": "https://example.org/schema.json",
      "$id": "urn:report:settings",
      "endpoint": "${s.endpoint}",
      "maxRetries": ${s.retries},
      "$$meta": { "generatedBy": "kotlin/variables-types refresher" }
    }
    """.trimIndent()
    return json
}

fun main() {
    val env = mapOf("ENDPOINT" to "https://api.example.com", "RETRIES" to "5")
    val settings = parseSettings(env)
    println(buildJsonReport(settings))
}

Notes:

  • parseSettings uses Nothing via fail(…) to assert non-nullability without extra else branches.
  • buildJsonReport demonstrates $$$ multi-dollar interpolation so JSON “$schema”, “$id”, and “$$meta” survive as literal keys. (kotlinlang.org)

Common Pitfalls and Tradeoffs

  • val isn’t deep immutability: val userIds = mutableListOf(…) prevents rebinding userIds, but elements can still change. Prefer immutable collections (e.g., List from listOf) or expose read-only views to callers.
  • “Just make it var”: mutability expands the state space. Keep var local and minimal; promote to val when a value stabilizes.
  • Inference surprises with null:
    • val x = null infers type Nothing?; you cannot later assign a String? to x. Fix by declaring an explicit type: var x: String? = null. (kotlinlang.org)
  • String templates in raw strings:
    • Raw strings don’t support backslash escapes; before 2.2 you had to write ${’$’} to get a literal $. On 2.2+, prefer multi-dollar interpolation for better readability. (kotlinlang.org)
  • Primitives and equality:
    • On JVM, boxed numeric objects may be cached; don’t use === to compare numeric values—use ==. (kotlinlang.org)
  • Boolean logic and side effects:
    • Rely on short-circuiting to prevent expensive or unsafe RHS evaluation: ok && perform(). (kotlinlang.org)
  • Char vs grapheme:
    • Char is a 16‑bit code unit; some visual characters require multiple code units. Prefer String for user-facing text. (kotlinlang.org)
  • K2 inference changes:
    • If an upgrade breaks inference in edge cases (e.g., builder inference or annotation generics), add explicit types or type arguments per the K2 migration guidance. (kotlinlang.org)

Technical Note

If you see mixed guidance about “multi-dollar string interpolation” being Experimental versus Stable:

  • The Strings guide page introduced it and historically marked it Experimental. (kotlinlang.org)
  • Kotlin 2.2 “What’s New” states multi-dollar interpolation is Stable; prefer that status on Kotlin ≥ 2.2. (kotlinlang.org)

Sources & Further Reading

Check Your Work

Hands-on Exercise

  • Implement a small CLI that:
    1. Reads ENDPOINT (String) and RETRIES (Int) from process args or environment.
    2. Uses val wherever possible, var only for a local counter.
    3. Builds a multiline JSON string containing keys $schema, $id, and $$meta using multi-dollar string interpolation. Print it.
    4. Add a fail(reason: String): Nothing helper and use it to validate that ENDPOINT is present.
    5. Demonstrate Char vs String by safely extracting the first code unit of ENDPOINT (if non-empty) and printing its code.

Brain Teaser

  • What is the inferred type of val x = null? Why can’t you later assign a String? Fix it without using an initializer. (Answer: x is inferred as Nothing?; declare var x: String? instead.) (kotlinlang.org)
  • Given val a: Int = 100; val b: Int? = a; val c: Int? = a, what does println(b === c) print and why? When would println(b == c) differ? (Hint: boxing and Integer caching; prefer structural equality.) (kotlinlang.org)
  • In Kotlin 2.2+, how do you include a literal $ in a template without writing ${’$’}? Provide two alternatives and explain when you’d choose each. (Hint: multi-dollar interpolation; $$ or $$$ prefixes vs the older ${’$’} escape.) (kotlinlang.org)

References

Share

More to explore

Keep exploring

Previous

Weekly Engineering Mastery Quiz (2026-03-09 to 2026-03-13)

Next

Flutter: Core Widget Concepts Refresher