Basic types and type inference
Kotlin is statically typed: every value has a type known at compile time, and the compiler uses it to catch mistakes. But thanks to inference you rarely write types out. This lesson covers the basic types you will use daily, the arithmetic surprises worth knowing, and how Kotlin's "everything is an object" design differs from Java's.
The basic types
val age: Int = 33 // 32-bit whole number
val population: Long = 3_500_000L // 64-bit whole number; note the underscores and L
val price: Double = 199.99 // 64-bit floating point (the default for decimals)
val rating: Float = 4.5f // 32-bit floating point; note the f
val initial: Char = 'K' // a single character, in single quotes
val active: Boolean = true // true or false
val city: String = "Pune" // text, in double quotes
The numbers you will use most are Int (whole numbers) and Double (decimals) — those are
what inference picks by default, so val n = 5 is an Int and val x = 5.0 is a Double. Reach
for the others deliberately: Long when a count might exceed about two billion (Int's ceiling),
Float rarely (it is less precise; prefer Double).
Two readability touches in there: underscores in numeric literals (3_500_000) are ignored by
the compiler and make big numbers legible, and the L and f suffixes tell Kotlin you want a
Long or a Float specifically.
Kotlin has no primitives — everything is an object
In Java, int (a primitive) and Integer (an object) are different things, and the distinction
trips people up constantly. Kotlin has only Int — one type, and it behaves like an object,
with methods:
val n = 42
println(n.toString()) // "42"
println(n.toDouble()) // 42.0
println((-5).absoluteValue) // 5
You get the convenience of objects with no performance penalty — the compiler quietly uses efficient primitives underneath where it can. You never think about it; you just call methods on numbers.
Conversions are explicit — and this catches Java people
Kotlin does not automatically widen one number type to another. This will not compile:
val i: Int = 10
val l: Long = i // error: type mismatch: inferred type is Int but Long was expected
You must convert on purpose:
val l: Long = i.toLong() // explicit, clear
Every numeric type has .toInt(), .toLong(), .toDouble(), .toFloat(), .toByte(),
.toShort(), .toChar(). This feels strict at first, but it removes a genuine source of subtle
bugs — an accidental narrowing that silently loses data. Kotlin makes you say what you mean.
The arithmetic surprise everyone hits
Integer division truncates — it throws away the fractional part, it does not round:
println(7 / 2) // 3, not 3.5
println(1 / 2) // 0
println(9 / 10) // 0
This is the single most common beginner surprise. 7 / 2 is 3 because both operands are Int, so
Kotlin does integer division. To get 3.5, at least one operand must be a Double:
println(7.0 / 2) // 3.5
println(7 / 2.0) // 3.5
println(7.toDouble() / 2) // 3.5
The classic bug is val average = total / count where both are Int — the average silently loses
its decimals. If you want a decimal answer, make sure a Double is involved. And % gives the
remainder: 7 % 2 is 1.
Overflow is silent — know it exists
An Int holds up to about 2.1 billion. Exceed it and it wraps around without any error:
val max = Int.MAX_VALUE // 2147483647
println(max + 1) // -2147483648 (wrapped to the most negative Int!)
This is exactly the "use bigint, not int, for an id" lesson from databases, in another
language: a counter that will grow large needs Long, or one day it silently goes negative. Kotlin
will not warn you — the type is your responsibility.
Booleans and comparisons
Boolean is true or false, produced by comparisons and combined with logical operators:
val a = 5
val b = 10
println(a < b) // true
println(a == b) // false
println(a != b) // true
println(a < b && b < 20) // true — && is "and"
println(a > b || b < 20) // true — || is "or"
println(!(a == b)) // true — ! is "not"
&& and || short-circuit: || stops at the first true, && at the first false. That
matters when the right side is expensive or unsafe to evaluate — a fact the null-safety module
leans on heavily.
== compares values, not identity
A crucial difference from Java, and a pleasant one. In Kotlin, == compares values (it calls
equals), and === compares identity (whether they are literally the same object):
val x = "Pune"
val y = "Pune"
println(x == y) // true — same text
In Java, == on objects compares identity, so comparing two strings with == is a classic bug.
Kotlin fixes the default: == does what you almost always mean. You will rarely need ===.
Check your work
The two number types you use most. Int (whole) and Double (decimal) — the inference
defaults.
When to reach for Long. When a value might exceed Int's ceiling of about 2.1 billion.
How Kotlin's types differ from Java's primitives. Kotlin has no primitives — Int is an object
with methods, but as efficient as a primitive underneath.
Why val l: Long = anInt fails. Kotlin does not auto-convert number types; use .toLong().
Why 7 / 2 is 3. Both operands are Int, so integer division truncates; make one a Double
for 3.5.
What happens at Int.MAX_VALUE + 1. It wraps silently to the most negative Int — no error.
What && and || short-circuit means. They stop as soon as the result is known, skipping the
right side.
What == does in Kotlin, versus ===. == compares values (calls equals); === compares
identity. Unlike Java, == is the one you almost always want.
Practice
- Declare a value of each basic type with an explicit annotation, then again with inference, and confirm (in the IDE) the inferred types match.
- Write
val l: Long = someIntand read the error. Fix it with.toLong(). - Print
7 / 2,7.0 / 2, and7 % 2. Explain each result. - Write the average bug:
val avg = 7 / 2and print it. Then fix it to get3.5. - Print
Int.MAX_VALUE, thenInt.MAX_VALUE + 1. Watch it wrap. Then do the same withLong. - Call three methods on an
Int(toString,toDouble, and.absoluteValueon a negative) to prove numbers are objects. - Compare two equal strings with
==and print the result. Then read what===would give and reason about why. - Use
3_500_000and confirm the underscores do not change the value.
Official documentation
- Kotlin — Basic types — The full set, with sizes and literals.
- Kotlin — Numbers — Conversions, operations, and the overflow behaviour.
- Kotlin — Equality —
==versus===, in detail. - Kotlin — Booleans — Logical operators and short-circuiting.
Next: strings, and the templates that make them readable.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship