Polyglot
Languages Kotlin syntax
Kotlin § syntax

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’s File.
  • .map { line -> ... } — trailing lambda.
  • val (name, age) = line.split(",") — destructuring.
  • .sortedBy { it.age } — implicit it parameter.
  • 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:

FormUseExample
lowerCamelCasefunctions, properties, parameters, local variablesuserName, toString()
UpperCamelCaseclasses, interfaces, objects, type parametersPerson, Comparable, T
lowerCamelCaseenum entries (preferred) or UPPER_SNAKE_CASEenum class Status { active, inactive } or ACTIVE, INACTIVE
UPPER_SNAKE_CASEtop-level / object constantsconst val MAX_RETRIES = 3
lowerCamelCasepackage 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 val by default — admit reassignment only when required.
  • Use var for 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 delegationby 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 memberscompanion object is the conventional substitute.
  • No raw arrays without typeArray<Int>, IntArray, etc.
  • No semicolons required — newlines terminate.
  • No ternary operator (?:)if as 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.