Course outline

Kotlin / Language foundations and explicit state / Lesson 1

Run a Kotlin program and reason about values

Understand a JVM-oriented entry point, val and var, inferred types, arithmetic, and explicit conversions.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain what the compiler infers and what a declaration promises.
  • Predict arithmetic and output without confusing a read-only reference with an immutable object.

Separate source code, compilation, and execution

Kotlin targets several platforms. This course uses JVM-oriented plain source examples unless a lesson explicitly states otherwise; Android, JavaScript, and Native have different platform libraries and build requirements. A top-level main function is a program entry point, and println writes a textual representation followed by a line break. Source code must first be compiled or evaluated by suitable Kotlin tooling. Java being installed does not prove that a Kotlin compiler is available. No compiler was present in this authoring environment, so expected outputs are reasoned examples and runnable flags remain false.

Choose val unless reassignment is needed

A val local is assigned once; a var local can receive another value of its declared type. Type inference chooses a type from the initializer, not from every future use. A count initialized with an integer literal is normally inferred as Int, so later assigning a fractional value is a type mismatch rather than automatic widening. Prefer meaningful names and explicit units, such as minutes or cents. A concise declaration should still let a reader understand the valueโ€™s role and the range the surrounding algorithm expects.

kotlin Compute remaining seats with distinct assignment roles
fun main() {
    val capacity = 8
    var reserved = 2
    reserved += 1
    val remaining: Int = capacity - reserved
    println("remaining=$remaining")
    println("capacity type remains Int")
}
Expected output
remaining=5
capacity type remains Int

The expected lines are remaining=5 and the literal type note. capacity is never reassigned, while reserved is. This complete teaching program was not compiled in the authoring environment.

Division follows operand types

When both operands are integers, division produces an integer result and discards the fractional part toward zero. Making one operand Double requests floating-point division. An explicit conversion such as toDouble changes the represented numeric valueโ€™s type for the operation; a cast is not a general numeric conversion. Floating-point values cannot exactly represent every decimal fraction. Use an appropriate money representation or domain library for financial rules rather than assuming a displayed decimal proves exact accounting. Integer arithmetic also has bounded ranges, so large inputs require deliberate range reasoning.

Keep conversion visible at the calculation boundary

The example contrasts integer and floating-point results using the same positive inputs. String templates insert a value using a dollar-prefixed name or a braced expression. A template changes presentation, not arithmetic semantics: division inside braces still follows operand types. Avoid relying on formatting to fix a calculation performed in the wrong type. When combining durations or sizes, convert to a common unit before the operation and record whether rounding is intended. The expected output is a language-level prediction, not a measured performance result.

kotlin Compare two division types
fun main() {
    val tasks = 5
    val workers = 2
    println(tasks / workers)
    println(tasks.toDouble() / workers)
    println("workers=$workers")
}
Expected output
2
2.5
workers=2

Expected output is 2, then 2.5, then workers=2. Converting tasks before division preserves the fractional result; converting the already truncated result would not. No compiler execution is claimed.

Read-only binding does not freeze an object

A val can refer to a mutable collection whose contents still change. It prevents assigning a different collection to that variable, not mutation through the referenced object. Likewise, a read-only interface may view an object that another reference can mutate. These distinctions become important when sharing state between functions or concurrent tasks. Begin with the simplest value that expresses the domain and restrict mutation to an explicit owner. Use small deterministic examples while learning, then introduce input and platform dependencies only when their error and lifetime behavior are understood.

Common errors and fixes

Val cannot be reassigned.

Why it happens
A read-only binding was used for a value that must be replaced.
How to fix it
Use var for deliberate reassignment or compute a new val.
Prevent it next time
Decide whether the algorithm needs state or a derived value.

The fractional part disappears.

Why it happens
Both division operands were integers.
How to fix it
Convert an operand before division when fractional semantics are intended.
Prevent it next time
Include a non-divisible case in examples and tests.

A val list changes unexpectedly.

Why it happens
Binding immutability was confused with object immutability.
How to fix it
Inspect mutable aliases and choose ownership or a defensive copy.
Prevent it next time
Document whether values are snapshots or shared views.

Check your understanding

1. Predict 7 / 2 and 7.toDouble() / 2. Explain the difference.

Show worked solution

The first is the integer value 3; the second is the Double value 3.5. The conversion occurs before division in the second expression. Writing (7 / 2).toDouble() would produce 3.0 because the fractional part was already discarded. Select the operation type before attempting to format the result.

2. A seat count is never reassigned but a mutable list of reservations grows. Which can use val?

Show worked solution

Both bindings can use val. The number remains the same value, and the list reference remains the same object even while its contents change. This does not make the list safe for concurrent mutation or protect a capacity invariant. Those guarantees need a separate design.

Key takeaways

  • Types determine operations, and val controls reassignment.
  • Expected output is distinct from a compiler-tested result.

Official references