Syntax
Kotlin’s syntax is concise and expression-oriented — type inference admits omitting most type annotations, semicolons are not required (newlines terminate statements), parentheses around if and while conditions are required (unlike Swift), and the conventional discipline favours expression-orientation (most constructs return values). The val/var distinction admits clear immutability marking. Kotlin admits first-class functions, trailing lambdas, string templates, type inference, and null safety as a type-system feature. The combination — concise syntax, type inference, expression-oriented constructs (if/when/try as expressions), the val-default discipline, the substantial Java interoperability — is the substance of Kotlin’s syntactic identity.
This page covers the surface a working programmer encounters routinely.
A complete program
The classical hello world:
fun main() {
println("Hello, world!")
}
A more substantial example:
import java.io.File
data class Person(val name: String, val age: Int) {
fun greeting() = "Hello, $name."
}
fun main() {
val people = File("people.txt")
.readLines()
.map { line ->
val (name, age) = line.split(",")
Person(name.trim(), age.trim().toInt())
}
.sortedBy { it.age }
people.forEach { println(it.greeting()) }
}
The principal features visible:
import java.io.File— Java standard library import.data class Person(...)— primary constructor, auto-generated equals/hashCode/toString/copy.fun greeting() = "Hello, $name."— single-expression function."Hello, $name."— string template.File("...").readLines()— Kotlin extension on Java’sFile..map { line -> ... }— trailing lambda.val (name, age) = line.split(",")— destructuring..sortedBy { it.age }— implicititparameter.people.forEach { ... }— lambda iteration.
Compilation and execution:
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar
For project-style work, the Gradle build system with the Kotlin DSL:
./gradlew run
Source character set
Kotlin source is interpreted as UTF-8. Identifiers may use Unicode letters; ASCII identifiers are conventional in code.
Identifiers and naming conventions
The conventional Kotlin naming follows the Kotlin Coding Conventions:
| Form | Use | Example |
|---|---|---|
lowerCamelCase | functions, properties, parameters, local variables | userName, toString() |
UpperCamelCase | classes, interfaces, objects, type parameters | Person, Comparable, T |
lowerCamelCase | enum entries (preferred) or UPPER_SNAKE_CASE | enum class Status { active, inactive } or ACTIVE, INACTIVE |
UPPER_SNAKE_CASE | top-level / object constants | const val MAX_RETRIES = 3 |
lowerCamelCase | package and file names (single word lowercase preferred) | package com.example.util |
The convention is enforced by community consensus and IDE tooling.
Reserved keywords
The reserved keywords:
as as? break class continue
do else false for fun
if in !in interface is
!is null object package return
super this throw true try
typealias typeof val var when
while
Plus soft keywords (reserved only in specific contexts):
by catch constructor companion crossinline
data dynamic enum external final
finally get import infix init
inline inner internal lateinit noinline
open operator out override private
protected public reified sealed set
suspend tailrec vararg
For using a reserved word as an identifier, the backtick form:
val `class` = "Math 101" // class is reserved
fun `function with spaces`() { } // admitted but conventional only in tests
The form is conventional only in test code (test names that read as sentences).
Comments
Three comment forms:
// A single-line comment, terminated by the end of the line.
/* A block comment.
Block comments admit nesting:
/* nested comment */
*/
/**
* A KDoc comment for the next item.
* Admits Markdown formatting and tags.
*
* @param name the name to greet
* @return the greeting string
*/
fun greet(name: String): String = "Hello, $name"
The /** ... */ form is KDoc — Kotlin’s documentation comment, consumed by the Dokka tool to produce structured documentation.
Statement terminators
Newlines terminate statements; semicolons are not required:
val x = 5
val y = 10
val z = x + y
Semicolons admit multiple statements on one line (rare):
val x = 5; val y = 10; val z = x + y
The conventional discipline omits semicolons.
Variable declarations
Two principal forms:
val x = 5 // immutable (preferred)
var y = 10 // mutable
val name: String = "Alice" // explicit type
val name = "Alice" // type inferred as String
var count: Int = 0
var count = 0 // type inferred as Int
The conventional discipline:
- Use
valby default — admit reassignment only when required. - Use
varfor genuinely mutable state. - Trust type inference — let the compiler determine when the initialiser is unambiguous.
For compile-time constants:
const val MAX_RETRIES = 3 // compile-time constant
const val GREETING = "Hello"
The const val admits inlining at the call site; restricted to top-level or object declarations.
For delayed initialisation of var:
lateinit var service: Service // initialised later
fun init() {
service = Service()
}
The lateinit admits non-null types initialised after construction; treated in Nullability.
Type annotations
Type annotations follow the colon convention:
val name: String = "Alice"
val age: Int = 30
val pi: Double = 3.14159
val isActive: Boolean = true
val values: List<Int> = listOf(1, 2, 3)
val lookup: Map<String, Int> = mapOf("a" to 1, "b" to 2)
fun add(a: Int, b: Int): Int {
return a + b
}
The Kotlin type system is strict — implicit numeric conversions are not admitted:
val n: Int = 5
val d: Double = n // ERROR
val d: Double = n.toDouble() // OK
The strictness eliminates a substantial class of conversion bugs.
Functions
The fun keyword introduces a function:
fun add(a: Int, b: Int): Int {
return a + b
}
fun greet(name: String): String {
return "Hello, $name"
}
fun performAction(): Unit { // explicit Unit (rarely needed)
println("acting")
}
fun performAction() { // Unit return type implicit
println("acting")
}
The form: fun name(params): ReturnType { body }. The return type comes after the colon; Unit (the empty type, equivalent to void) is the default if omitted.
Single-expression functions
Functions returning a single expression admit the = form:
fun add(a: Int, b: Int): Int = a + b
fun greet(name: String) = "Hello, $name" // return type inferred
fun square(n: Int) = n * n
The conventional discipline favours the = form for short functions.
Treated in Functions and lambdas.
Block syntax
Control-flow constructs require braces; the parentheses around the condition are required:
if (condition) {
doSomething()
}
if (condition) doSomething() // OK; one-liner
while (!done) {
advance()
}
The braces are required for multi-statement bodies; admitted for single-statement bodies (the conventional discipline includes braces for consistency).
String templates
Double-quoted strings admit $variable and ${expression} interpolation:
val name = "Alice"
val greeting = "Hello, $name!"
val total = 42
val formatted = "Total: $total items, $${total * 2} after doubling"
// Expression in braces:
val description = "Pi: ${Math.PI}, rounded: ${"%.2f".format(Math.PI)}"
// Nested:
val outer = "outer: ${"inner: ${42}"}"
The mechanism admits substantial conciseness; treated in Strings.
Expression-oriented constructs
Several Kotlin constructs are expressions — they produce values:
// if as expression:
val max = if (a > b) a else b
val status = if (user.isActive) "active"
else if (user.isPending) "pending"
else "inactive"
// when as expression:
val category = when {
n < 0 -> "negative"
n == 0 -> "zero"
n < 100 -> "small"
else -> "large"
}
// try as expression:
val n = try {
parseInt(input)
} catch (e: NumberFormatException) {
0
}
The expression-orientation admits substantial conciseness; treated in Conditionals and Pattern matching.
Null safety
Kotlin distinguishes nullable and non-nullable types in the type system:
val name: String = "Alice" // non-nullable
val name: String = null // ERROR
val nullable: String? = "Alice" // nullable
val nullable: String? = null // OK
// Safe call:
val length = nullable?.length // Int?
// Elvis:
val length = nullable?.length ?: 0 // Int
// Force unwrap:
val length = nullable!!.length // Int (NullPointerException if null)
Treated in Nullability.
Trailing lambdas
When the last argument to a function is a lambda, it admits outside the parentheses:
listOf(1, 2, 3).map({ x -> x * 2 }) // lambda inside parens
listOf(1, 2, 3).map { x -> x * 2 } // trailing lambda
listOf(1, 2, 3).map { it * 2 } // implicit `it`
// With other args:
runCatching { fetchData() }
// function takes one lambda
buildString {
append("Hello, ")
append("World!")
}
// Multiple args:
listOf(1, 2, 3).fold(0) { acc, x -> acc + x } // fold(initial, lambda)
The mechanism admits substantial DSL-style syntax — conventional in build scripts (Gradle), test specifications, and similar.
Packages and imports
Each Kotlin file may declare a package:
package com.example.app
import java.util.Date
import kotlin.math.PI
import kotlin.collections.List // typically auto-imported
import com.example.lib.helper as h // alias
fun main() {
/* ... */
}
The import grammar admits aliasing via as. Treated in Scope and packages.
A note on what Kotlin admits
Several distinguishing features:
- Null safety in the type system — explicit nullable vs non-nullable.
- Data classes — auto-generated equals/hashCode/toString/copy.
- Sealed classes/interfaces — closed type hierarchies for ADTs.
- Extension functions — adding methods to existing types.
- Coroutines — structured concurrency with suspend functions.
- Property delegation —
by lazy,by Delegates.observable, etc. - Smart casts — automatic narrowing in conditionals.
- Operator overloading — declared via
operator fun. - Type inference — substantial.
- Inline functions — substantial performance for higher-order functions.
- Reified type parameters — runtime access to generic types in inline functions.
A note on what is not in Kotlin
- No primitive types in source — Int, Double, etc. are object types (compile to JVM primitives where possible).
- No checked exceptions — admit but not enforced.
- No static members —
companion objectis the conventional substitute. - No raw arrays without type —
Array<Int>,IntArray, etc. - No semicolons required — newlines terminate.
- No ternary operator (
?:) —ifas expression admits the use case. - No fall-through in
when— each branch is independent.
The combination — concise syntax, expression-orientation, null safety, full Java interoperability, the substantial functional-and-OO blend, structured concurrency through coroutines — is the substance of Kotlin’s identity. The discipline produces clear, type-safe, expressive code with substantial Java-ecosystem reuse.