Free Handbook · Every example compiled & verified

Fundamentals

val versus var, type inference, the basic types, strings and templates, operators, reading input with readln(), converting types safely, and ranges.

0 / 136 lessons🔥 0 day streak
ShareXLinkedIn

Module 01 · what you'll be able to do

  • Choose between val and var, and explain why Kotlin code defaults to val
  • Name the basic types (Int, Long, Double, Boolean, Char, String) and know when the compiler infers each
  • Build strings with templates and raw strings, and use the common String functions
  • Read a line of input with readln() and convert it with toInt() or the safe toIntOrNull()
  • Write ranges with .., until and step, and test membership with in
01

val vs var

Kotlin has two keywords for declaring a variable. val (value) is read-only: it is assigned once and can never point at something else. var (variable) can be reassigned. Idiomatic Kotlin uses val everywhere it can and reaches for var only when a value genuinely changes — a counter, a running total. Fewer things that change means fewer places a bug can hide.

kotlinMain.kt
fun main() {
    val name = "Asha"      // read-only
    var score = 10         // reassignable
    score = score + 5
    score += 5
    println("$name scored $score")
}
Outputcompiled & run with real Kotlin
Asha scored 20
Your turn

Add score -= 3 before the println and predict the new output.

Error you will hit

val cannot be reassigned

kotlin
fun main() {
    val count = 1
    count = 2
    println(count)
}
Main.kt:3:5: error: 'val' cannot be reassigned.
Why the compiler said that

count is a val, so the compiler guarantees it keeps its first value. Assigning again breaks that promise and the file does not compile.

The fix

If the value really must change, declare it with var. If it should not, create a new val with a different name instead.

kotlin
fun main() {
    var count = 1
    count = 2
    println(count)
}
val is read-only, not deeply immutable
val list = mutableListOf(1, 2) cannot be pointed at a different list, but list.add(3) still works — the reference is fixed, the object it points to can change. Module 05 covers read-only versus mutable collections.
02

Basic types and type inference

Every value in Kotlin has a type that is fixed at compile time. You rarely write it, because the compiler infers it from the initial value: 42 is an Int, 42L a Long, 3.14 a Double, 3.14f a Float, 'A' a Char. Write the type after a colon when you want to be explicit or when there is no initial value.

kotlinMain.kt
fun main() {
    val age = 30                  // Int
    val population = 8_100_000_000L // Long (underscores are ignored)
    val price = 19.99             // Double
    val ratio: Float = 0.5f
    val initial = 'K'             // Char
    val active = true             // Boolean
    val city: String = "Pune"

    println("$age $population $price $ratio $initial $active $city")
    println("Int max: ${Int.MAX_VALUE}")
    println("Long max: ${Long.MAX_VALUE}")
}
Outputcompiled & run with real Kotlin
30 8100000000 19.99 0.5 K true Pune
Int max: 2147483647
Long max: 9223372036854775807
Your turn

Print Int.MAX_VALUE + 1. Integers overflow silently on the JVM — what do you get?

TypeSizeExample literalUse for
Int32 bits42Counts, indexes, most whole numbers
Long64 bits42LTimestamps, IDs, anything above about 2.1 billion
Double64 bits3.14Decimal numbers (default)
Float32 bits3.14fGraphics, Android APIs that ask for it
Boolean—true, falseConditions
Char16 bits'a'A single UTF-16 character
String—"text"Text
Byte, Short8, 16 bitsval b: Byte = 1Binary data, rarely elsewhere
Error you will hit

Initializer type mismatch

kotlin
fun main() {
    val count: Int = "12"
    println(count)
}
Main.kt:2:22: error: initializer type mismatch: expected 'Int', actual 'String'.
Why the compiler said that

Once a type is declared, only values of that type fit. "12" is text that happens to contain digits — Kotlin never converts it to a number behind your back.

The fix

Use a number literal, or convert the string explicitly with "12".toInt() (see the conversion lesson below).

kotlin
fun main() {
    val count: Int = "12".toInt()
    println(count)
}
Money is not a Double
0.1 + 0.2 is 0.30000000000000004 in every language that uses binary floating point. In real payment code, store money as a Long number of the smallest unit (paise, cents) or use java.math.BigDecimal.
03

Strings and string templates

Strings are immutable: every function that "changes" a string returns a new one. String templates put values inside a string: $name for a plain variable, ${...} for any expression, such as a property, a function call or arithmetic. Raw strings in triple quotes keep newlines and backslashes as-is; trimIndent() removes the common leading indentation.

kotlinMain.kt
fun main() {
    val first = "Grace"
    val last = "Hopper"
    val full = "$first $last"

    println(full)
    println("Length: ${full.length}")
    println(full.uppercase())
    println(full.lowercase())
    println(full.substring(0, 5))
    println(full.contains("Hop"))
    println(full.replace("Grace", "G."))
    println("  padded  ".trim() + "|")
    println(full[0])

    val poem = """
        Roses are red,
        Kotlin is neat.
    """.trimIndent()
    println(poem)
}
Outputcompiled & run with real Kotlin
Grace Hopper
Length: 12
GRACE HOPPER
grace hopper
Grace
true
G. Hopper
padded|
G
Roses are red,
Kotlin is neat.
Your turn

Print the last character of full using full[full.length - 1], then with full.last().

Comparing strings
Use == to compare strings in Kotlin — it compares the content (it calls equals). This is the opposite of Java, where == on strings compares references and is a classic bug. === is Kotlin's reference comparison, and you almost never need it for strings.
04

Operators

Arithmetic works as you expect, with one trap: dividing two Ints gives an Int — the fraction is thrown away. Make one side a Double to get a decimal result. % is the remainder. Comparison operators return a Boolean, and &&, || and ! combine booleans with short-circuiting (the right side is skipped when the left already decides the answer).

kotlinMain.kt
fun main() {
    val a = 17
    val b = 5
    println(a + b)
    println(a - b)
    println(a * b)
    println(a / b)          // Int / Int -> Int
    println(a % b)          // remainder
    println(a / b.toDouble())

    var n = 1
    n++
    n *= 10
    println(n)

    println(a > b && b > 0)
    println(a < b || b == 5)
    println(!(a == b))
    println("kotlin" == "kot" + "lin")
}
Outputcompiled & run with real Kotlin
22
12
85
3
2
3.4
20
true
true
true
true
Your turn

Compute the average of 7 and 8 so that it prints 7.5, not 7.

OperatorMeaningExample
+ - * / %Arithmetic17 / 5 == 3, 17 % 5 == 2
+= -= *= /= %=Update a vartotal += price
++ --Add or subtract onei++
== !=Structural equality (calls equals)"a" == "a"
=== !==Reference equality (same object)Rarely needed
< > <= >=Comparison (calls compareTo)age >= 18
&& || !Logical and, or, notok && !done
in !inMembership in a range or collectionx in 1..10
05

Reading input with readln()

readln() reads one line from standard input and returns it as a String (without the newline). It throws if input has ended; readlnOrNull() returns null instead. Input is always text — convert it to a number yourself. The example below was run with the two input lines shown under "stdin".

kotlinMain.kt
fun main() {
    print("Your name: ")
    val name = readln()
    print("Your age: ")
    val age = readln().toInt()
    println()
    println("Hi $name, next year you will be ${age + 1}.")
}
Outputcompiled & run with real Kotlin
Your name: Your age: 
Hi Meera, next year you will be 25.

print() does not add a newline, so the prompts share a line here — input typed at a terminal would appear after each prompt.

Your turn

Read a third line, a city, and include it in the greeting.

kotlinMain.kt
fun main() {
    // Several numbers on one line, separated by spaces
    val numbers = readln().split(" ").map { it.toInt() }
    println("Count: ${numbers.size}")
    println("Sum: ${numbers.sum()}")
    println("Max: ${numbers.max()}")
}
Outputcompiled & run with real Kotlin
Count: 4
Sum: 22
Max: 9

The pattern you will use in every coding-round problem: read a line, split it, convert each piece.

06

Type conversion: toInt() and toIntOrNull()

Kotlin never converts between number types implicitly — not even from Int to Long, which Java does silently. You call a conversion function: toInt(), toLong(), toDouble(), toString(). Converting a String to a number can fail, so there are two versions: toInt() throws on bad input, toIntOrNull() returns null, which you then handle with ?: (the "Elvis" operator, Module 04).

kotlinMain.kt
fun main() {
    val small: Int = 42
    val big: Long = small.toLong()
    val d: Double = small.toDouble()
    println("$big $d")

    println(3.99.toInt())         // truncates toward zero
    println("123".toInt() + 1)
    println(65.toChar())
    println('A'.code)

    val inputs = listOf("7", "seven", "12")
    for (s in inputs) {
        val n = s.toIntOrNull() ?: 0
        println("'$s' -> $n")
    }
}
Outputcompiled & run with real Kotlin
42 42.0
3
124
A
65
'7' -> 7
'seven' -> 0
'12' -> 12
Your turn

Change the fallback so an invalid string prints -1 instead of 0.

Error you will hit

No implicit widening from Int to Long

kotlin
fun main() {
    val small: Int = 42
    val big: Long = small
    println(big)
}
Main.kt:3:21: error: initializer type mismatch: expected 'Long', actual 'Int'.
Why the compiler said that

Java would widen an int to a long automatically. Kotlin treats Int and Long as unrelated types, so every conversion is visible in the code.

The fix

Call small.toLong().

kotlin
fun main() {
    val small: Int = 42
    val big: Long = small.toLong()
    println(big)
}
Error you will hit

NumberFormatException from toInt()

kotlin
fun main() {
    val input = "12a"
    val n = input.toInt()
    println(n + 1)
}
Exception in thread "main" java.lang.NumberFormatException: For input string: "12a"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
	at java.base/java.lang.Integer.parseInt(Integer.java:662)
	at java.base/java.lang.Integer.parseInt(Integer.java:778)
	at MainKt.main(Main.kt:3)
Why the compiler said that

This one compiles — the problem only exists at runtime, when "12a" turns out not to be a number. toInt() calls Java's Integer.parseInt, which throws. Read the stack trace bottom-up: your code, Main.kt:3, is the last line.

The fix

For input you do not control (users, files, network), use toIntOrNull() and decide what happens when it returns null.

kotlin
fun main() {
    val input = "12a"
    val n = input.toIntOrNull()
    if (n == null) println("not a number") else println(n + 1)
}
07

Ranges

A range is a sequence of values between two ends. 1..5 includes both ends. 1 until 5 (or 1..<5) excludes the upper end — exactly the indexes of a 5-element list. step skips values and downTo counts backwards. in tests whether a value falls inside a range, and ranges work for Char too.

kotlinMain.kt
fun main() {
    println((1..5).toList())
    println((1 until 5).toList())
    println((1..<5).toList())
    println((0..10 step 3).toList())
    println((5 downTo 1).toList())
    println(('a'..'e').joinToString(""))

    val age = 17
    println(age in 13..19)
    println(age !in 0..12)
    println((1..100).sum())
}
Outputcompiled & run with real Kotlin
[1, 2, 3, 4, 5]
[1, 2, 3, 4]
[1, 2, 3, 4]
[0, 3, 6, 9]
[5, 4, 3, 2, 1]
abcde
true
true
5050
Your turn

Print the even numbers from 2 to 20 as a list using step.

VisualizeHow a range with step is walkedStep 1 / 5
fun main() {
for (i in 0..10 step 3) {
println(i)
}
}
Line 2

0..10 step 3 creates a progression that starts at 0 and adds 3 each time, stopping before it passes 10.

Variables now
i0
All 5 steps as a table
StepLineWhat happenedVariables now
120..10 step 3 creates a progression that starts at 0 and adds 3 each time, stopping before it passes 10.i = 0
23First value.i = 0
330 + 3.i = 3
433 + 3.i = 6
536 + 3. The next value, 12, is past 10, so the loop ends — 10 itself is never visited.i = 9
val
A read-only variable: assigned once, cannot be reassigned.
var
A mutable variable that can be reassigned.
Type inference
The compiler working out a variable's type from its initial value.
String template
$name or ${expression} inside a string literal.
Raw string
A """triple-quoted""" string that keeps newlines and backslashes literally.
readln()
Reads one line of standard input as a String; throws at end of input.
toIntOrNull()
Parses a String to Int, returning null instead of throwing on bad input.
Range
a..b (inclusive) or a until b / a..
Quick check

What does println(7 / 2) print in Kotlin?

Quick check

Which declaration compiles?

JuniorWhat is the difference between val and var, and which should you prefer?

val declares a read-only reference that is assigned exactly once; var can be reassigned. Prefer val: code where values do not change is easier to reason about and safe to share between threads. Note that val only fixes the reference — a val holding a MutableList can still have items added.

What they are really testing: Whether you know read-only is not the same as immutable.

Frequently asked questions

Should I use val or var in Kotlin?
Use val by default and switch to var only when the value must change, such as a loop counter or a running total. IntelliJ IDEA even suggests changing a var to val when it is never reassigned.
How do I read user input in Kotlin?
Call readln() to get one line as a String, then convert it: readln().toInt(). For input that might be invalid, use toIntOrNull() and handle the null. readlnOrNull() returns null at the end of input instead of throwing.
Why does Kotlin not convert Int to Long automatically?
Implicit numeric conversions hide bugs — Java's silent widening and narrowing rules are a common source of surprises. Kotlin makes every conversion explicit with toLong(), toDouble() and friends. Number literals still adapt to the declared type, so val x: Long = 5 is fine.

Finish the Kotlin handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.