Forge Fate
Today I Learned

2026-10-07 Kotlin Basics: Types, Functions, Unit, File Structure, if and when

3 min read

Today I started learning Kotlin: basic types, how to write functions, Unit, file structure, and how to write if and when. While writing this up, I compiled and ran every example to check the results.

Environment: Kotlin 2.2.20 (the kotlinc bundled with IntelliJ IDEA) on JVM 21. The outputs and error messages below are what this environment actually produced.

1. Basic types and variables

val and var

kotlin
val name = "Kotlin"   // cannot be reassigned (similar to a final variable in Java)var count = 0         // can be reassignedcount += 1

Use val by default, and var only when the value needs to change.

Type inference and basic types

You don't have to write the type; it is inferred from the assigned value. To write it explicitly, use name: Type.

kotlin
val i = 42               // Intval l = 3_000_000_000    // exceeds the Int range, so Longval l2 = 10L             // the L suffix makes it Longval d = 3.14             // Doubleval f = 2.5f             // the f suffix makes it Floatval b = true             // Booleanval c = 'A'              // Charval s = "Kotlin"         // Stringval explicit: Int = 42   // writing the type explicitly

Printing the type name of each value gives:

text
Int Long Long Double Float Boolean Char String

You can put _ between digits for readability, as in 3_000_000_000. Integer literals are Int by default, but are inferred as Long when they don't fit in Int.

Number types are not widened automatically

In Java you can assign an int to a long variable directly. In Kotlin you can't.

kotlin
val i: Int = 1val l: Long = i            // compile errorval ok: Long = i.toLong()  // must convert explicitly
text
error: initializer type mismatch: expected 'Long', actual 'Int'.

You call conversion functions such as toLong(), toInt(), and toDouble() explicitly.

String templates

Inside a string, $variable or ${expression} inserts a value.

kotlin
val a = 1val b = 2println("$a + $b = ${a + b}")   // 1 + 2 = 3

2. Writing functions

Basic form

kotlin
fun add(a: Int, b: Int): Int {    return a + b}
  • Functions start with the fun keyword.
  • Parameters are written name: Type, the opposite order of Java (int a).
  • The return type goes after the parentheses as : Type.

Expression body

If the body is a single expression, you can drop the braces and return and use =. The return type is then inferred too.

kotlin
fun multiply(a: Int, b: Int) = a * b

Default values and named arguments

kotlin
fun greet(name: String, greeting: String = "Hello"): String = "$greeting, $name!"greet("Kotlin")                        // Hello, Kotlin!greet(greeting = "Hi", name = "Kim")   // Hi, Kim!

Parameters can have default values, and naming arguments at the call site lets you pass them in any order. You don't need several overloads the way you would in Java.

3. Unit

A function that returns no meaningful value has the return type Unit. It plays the role of Java's void and can be omitted.

kotlin
fun printSum(a: Int, b: Int) {          // return type omitted = Unit    println("$a + $b = ${a + b}")}fun printSumExplicit(a: Int, b: Int): Unit {   // writing it is also fine    println("$a + $b = ${a + b}")}

The difference from void is that Unit is an actual value (an object). You can store the return value in a variable and print it.

kotlin
val r = printSum(1, 2)println(r)          // kotlin.Unitprintln(r == Unit)  // true
text
1 + 2 = 3kotlin.Unittrue

void means "no value", so it can't be stored in a variable. Unit is a type with exactly one value, so it can be handled like any other type.

4. File structure

kotlin
package study.basicsimport kotlin.math.maxfun main() {    println(max(3, 7))}
  • package goes at the top, followed by imports.
  • Functions can live at the top level of a file, outside any class. Unlike Java, you don't need a class just to hold main.
  • The entry point is fun main(). If you don't need the arguments, you can leave out args.
  • The file name doesn't have to match a class name, and one file can hold several classes and functions.

On the JVM, every function still has to live inside a class, so the compiler generates a FileNameKt class for top-level functions. Compiling Basics.kt produced BasicsKt.class, and that is the name used to run it.

text
$ kotlinc Basics.kt -d out$ ls out/study/basicsBasicsKt.class$ kotlin -cp out study.basics.BasicsKt

5. if is an expression

In Kotlin, if is an expression that returns a value. That's why there is no separate ternary operator (a > b ? a : b).

kotlin
fun maxOf2(a: Int, b: Int) = if (a > b) a else b

With a block, the value of the last expression becomes the result.

kotlin
val result = if (count > 0) {    println("positive block")    "positive"        // this becomes the value of result} else {    "zero"}

When used as an expression, else is required, because otherwise there would be no value when the condition is false.

kotlin
val z = if (x > 0) "p"   // compile error
text
error: 'if' must have both main and 'else' branches when used as an expression.

6. when

when replaces Java's switch. It needs no break, and it can also be used as an expression.

Values, multiple values, ranges

kotlin
fun grade(score: Int): String = when (score) {    100 -> "Perfect"    in 90..99 -> "A"          // 90 through 99    in 80 until 90 -> "B"     // 80 up to, but not including, 90    else -> "C or below"}fun dayType(day: String) = when (day) {    "Sat", "Sun" -> "Weekend" // several values separated by commas    else -> "Weekday"}

.. includes the end, and until excludes it.

Type checks and smart casts

kotlin
fun describe(x: Any): String = when (x) {    1 -> "The number 1"    "hello" -> "A greeting"    is Long -> "A Long"    !is String -> "Not a string"    else -> "Some other string: ${x.length}"}
text
describe(1)       → The number 1describe("hello") → A greetingdescribe(5L)      → A Longdescribe(2.0)     → Not a stringdescribe("abc")   → Some other string: 3

In the final else branch, x is declared as Any, yet x.length compiles. The earlier !is String branch already caught every non-string value, so the compiler treats x as a String here. This is called a smart cast, and no explicit cast is needed.

when without a subject

Without a subject in parentheses, each branch can be a condition. This replaces an if-else if chain.

kotlin
fun sign(n: Int) = when {    n < 0 -> "negative"    n == 0 -> "zero"    else -> "positive"}

As an expression, every case must be covered

When when is used as an expression, it must cover every possible case, usually with an else.

kotlin
val y = when (x) { 1 -> "one" }   // compile error
text
error: 'when' expression must be exhaustive. Add an 'else' branch.

Summary

TopicWhat to remember
Variablesval by default, var when the value changes
TypesMostly inferred. Numbers aren't widened automatically; convert with toLong() etc.
Functionsfun name(arg: Type): ReturnType; use = for a single expression
ArgumentsDefault values and named arguments reduce the need for overloads
UnitPlays the role of void, but is a type with one real value. Can be omitted
FilesTop-level functions are allowed; on the JVM they compile into a FileNameKt class
ifAn expression that returns a value. Replaces the ternary operator; else is required as an expression
whenReplaces switch. Supports multiple values, ranges, type checks; must be exhaustive as an expression
Report an error or share feedback

Open a draft with this article’s title and URL. Review the message and recipient before sending.

To: [email protected]

Open email draft

If no email app opens, copy these details into your usual email service.

Contact information