Jim H
Jim H
  • Home
  • Resume
  • Code Review-BestPractices
  • Java Best Practices
  • Kotlin Best Practices
  • Homophones in English
  • Personal
  • Blog

Best Practices in Kotlin

Hello, I'm Jim Hamilton!

This collection is based on my previous article, Best Practices in Java, and refers directly to that article in places. As there, some items are specific to Kotlin, some are specific to the interface between Kotlin and Java, and some are good practices that are independent of language.


Image credit:  https://kotlinlang.org.


Do you have corrections or suggestions? Send me an email.


Copyright © 2025 Jim Hamilton. All rights reserved.

Contents

General Best Practices 

Extensions

Operator Precedence

Null Safety

Immutability and open

Overriding Rules

Constant Definition and Readability

Numeric Types and Boxed Types

Strings and String Templates

Collections

Enumerations

Types of Classes

Throwables

Integer and Floating-point Arithmetic

Date & Time

Concurrent Execution

Coroutines

Data Integrity

Stateless Execution

Encapsulation

Type Aliases

Scope Functions

Serialization


Appendix: Code Snippets

General Best Practices

Back to Contents


These are strongly suggested, to make sure that when multiple engineers edit the same code, the only changes are to the code, not wholesale changes to files.  This makes code reviews consistent.

  • Consistency is good.  It helps the reviewer to know what to expect.  It helps the developer who looks at the code a year from now–who might be you.  (Pro tip: Always assume that the next developer who looks at your code has trouble managing anger and knows where you live. 🙃)
    • Always be consistent within a project.
    • It’s best to be consistent for all projects owned by your team.
  • There is no extra credit for making your code difficult to understand (unless you are writing an entry to something like the International Obfuscated C Code Contest).
  • On the same note: Comments are a Good Thing™.  They are useful any time reading the code is not “easy.”  But see the previous point about “difficult to understand” code.  If you can’t understand your own code, it’s a contest entry.  You should also be able to understand code your teammates write when you are reviewing their code.  If in doubt, ask!
  • Remember that your code is yours until it has been merged.  Once the code is merged, it’s ours, not merely yours.  That means anyone who is on duty may have to read, understand, and possibly change the code. The implication is that reviewing the code means accepting responsibility for the code.
  • Use meaningful identifier names, and in general, don't use single-letter names. It might feel appropriate to name your loop indices i, j, and k, but "outer", "middle", and "inner" will make more sense–especially a year from now, or to your successor. 
  • On the same subject: the rules for Kotlin identifiers are similar to other languages' rules: names may contain letters, numbers,  underscores, and escaped sequences; names must begin with a letter or _; they are case-sensitive, and they may not contain whitespace or special characters. However, non-alphanumeric characters may be used in identifiers if they are escaped with backquotes (`). And of course, they must not match reserved keywords, unless they are escaped.
  • Note: when Kotlin is run on the JVM, even escaped sequences may not include any of the following:  [, ], /, <, >, :, \\, . (Yes, that's two backslashes, the first one being an escape to allow the second one.)
  • Kotlin includes 28 "hard" keywords:  as, break, class, continue, do, else, false, for, fun, if, in, interface, is, null, object, package, return, super, this, throw, true, try, typealias, typeof, val, var, when, while
  • There are also "soft" keywords in Kotlin. For example, "public" is a keyword when it modifies the visibility of a member function or field, but in other contexts it's not a keyword. So you may declare var public = 1 for instance. Some soft keywords are: import, override, private, etc. The definitive list is here.
  • The Kotlin style guide specifies further guidelines here. Some of the highlights:
    • Package names are always lowercase, with no underscores.
    • Classes and objects use UpperCamelCase.
    • Functions, properties, and local variables use camelCase with no underscores.
    • Test methods may use escaped names with spaces or underscores.
    • Property names marked with const and are deeply immutable (including enum constants) should use SCREAMING_SNAKE_CASE.
    • If a class has two properties which are conceptually the same, but one is part of a public API and the other is an implementation detail, use an underscore as the prefix for the name of the private property.
    • If a class name starts with an initialism (such as XML), still use UpperCamelCase (XmlFormatter). Exception: for a two-letter initialism, keep both letters uppercase (IOStream).
    • Use four spaces for indentation. Do not use tabs.
    • For { blocks of statements }, put the opening brace on the end of the line where the construct begins, and put the closing brace on a separate line aligned horizontally with the opening construct.

for (elements in list) {

. . .

} 

Note: semicolons are optional in Kotlin, and therefore line breaks are significant. The language design assumes this brace style, and “you may encounter surprising behavior if you try to use a different formatting style.” (Kotlin Style Guide)

  • Put spaces around binary operators (a + b). Exception: do not put spaces around the “range to” operator (0..n).
  • Do not put spaces around unary operators (n++).
  • Put spaces between control flow keywords (if, when, for, while, do) and the opening parenthesis.
  • Do not put a space before the opening parenthesis in a primary constructor definition, method declaration, or method call.
  • Never put a space after ( or [, or before ] or ).
  • Never put a space around . or ?.
  • Put a space after //
  • Do not put spaces around angle brackets used for type parameters: List<String>
  • Do not put spaces around :: as in Foo::class or String::length.
  • Do not put a space before the ? Used to mark a nullable type: String?.
  • Put a space before : in the following situations:
    • When it’s used to separate a type and a supertype.
    • When delegating to a superclass constructor or a different constructor of the same class.
    • After the object keyword.
  • Do not put a space before : when it separates a declaration and its type.
  • Always put a space after :.
  • All that said, Kotlin uses Unicode throughout. Even though identifiers are restricted to letters, numbers, and _, there are thousands more letters than the 52 upper- and lower-case letters in ASCII. It is perfectly legal to name a variable "señor" or "façade," or even "पहचानकर्ता," but please don't give into temptation. (A good IDE will issue a warning about non-ASCII characters in an identifier.)
  • Turn on your IDE's helpers. This gives you some simple code generation, and tips such as pulling the return out of an if; swapping expressions on either side of a comma, and others. 
  • Teams should agree on the layout of classes and objects. The Kotlin style guide suggests:
    1. Property declarations and init blocks
    2. Secondary constructors
    3. Method declarations
    4. Companion object

  • The style guide says not to sort by alphabet or visibility, and not to separate regular methods from extension methods. Put related things together, so that the reader can follow the logic more easily. "Choose an order (either higher-level stuff first, or vice versa) and stick to it." 
  • Nested classes should go next to the code that uses them. If the classes are meant to be used externally and are not referenced inside the class, put them at the end, after the companion object.
  • When you implement an interface, put the implementing methods in the same order as members of the interface (possibly interspersed with additional private methods used for the implementation).
  • Always put overloads next to each other.
  • In the Java guide, I recommend always putting { braces } around single-instruction blocks. The reason for this is that a stray semicolon after the condition will detach an if from its conditional statement. In Kotlin, semicolons are optional, so this no longer applies.

Extension Functions

Back to Contents


(Not a thing in Java; but similar to Categories in Objective-C.) A Kotlin class may be "extended" by writing a new method for it, even without the original source code. Here is an example:


import kotlin.math.pow

import kotlin.math.round

fun Double.round(decimals: Int): Double {
   val multiplier = 10.0.pow(decimals)

    return round(this * multiplier) / multiplier
}


The library function, kotlin.math.round(Double), rounds to the nearest integer and returns a Double. This extension to Double lets you round to any number of decimal places – including a negative number, to round to the nearest ten or hundred.

val x = PI.round(2)  // 3.14

val y = 123.4.round(-1)  // 120


Things to remember about extension functions:

  • Extensions are resolved statically. For instance, if you define the same extension function for a base class and a derived class, and call it for a base class object reference, you will get the base class's extension.
  • If there is a member function with the same signature as the extension function, the member always "wins." (It's perfectly fine to overload a class function by an extension function with a different signature.)
  • Extensions can be defined for a nullable receiver. See Kotlin's definition of toString(), below.

Operator Precedence

Back to Contents


We all remember PEMDAS from elementary school, right? Parentheses, Exponents, Multiplication/Division, Addition/Subtraction. (I’ve seen some remember a different mnemonic, BEDMAS, where parentheses are called “braces,” and equal-precedence operations got switched, but that’s the same thing. Sounds like British Commonwealth; “countries separated by a common language” and all that.) Well, Kotlin has its own operator precedence.  It’s defined here. It’s important that you know this, or at the very least, know where to look it up.


Note: In the Java BP article, I wrote about the problem with the shift operators having a lower precedence than the equality operators. In Kotlin, the shl and shr operators have a higher precedence, just below the additive operators + and –. So that particular pitfall does not exist in Kotlin.

Avoid NullPointerException

Back to Contents


Step 1: Use Kotlin.  https://kotlinlang.org/docs/null-safety.html

Step 2: You're done!


All right, it isn't quite that simple. There are still ways to force a NullPointerException in Kotlin, but you literally have to go out of your way. If you have a nullable type, and it happens to be null, you can apply the not-null assertion operator !! and dereference it. Or, interaction with Java classes can put a null reference into a collection of a non-nullable type.

Or, you can explicitly throw NullPointerException().


Note: the page linked above is well worth your time. Null safety is the raison d'être for Kotlin.


Also: remember the features in Kotlin that help with null-safety:

  • ?. : safe call operator – a?.b checks a for null; its value is null if a is null, and a.b if a is not null.
  • ?: (the Elvis operator): a.b() ?: throw IllegalArgumentException() – if a is null, throw the exception; otherwise call b().
  • Nullable receiver – an extension function can have a nullable receiver type, so that they can be called on variables that might be null. The function will do a null check before dereference.  Consider the implementation of .toString():

fun Any?.toString(): String {

    if (this == null) return "null"

    return toString() 

}

Immutability and open

Back to Contents


A variable in Kotlin is declared either with var or val. The difference is that a var may be modified, but a val is final.

Unlike in Java, classes in Kotlin are final by default (unless they are declared abstract). To make a class inheritable, use the keyword open on the class; to make a method overridable, that method must also be declared open.

To declare an override method, the keyword override is required. Any method marked override is also open. To "close" an overridden function, it may be marked with final.

Properties may also be overridden, either in the body of the derived class, or in the declaration of the primary constructor, as in the class Square below. You may override a val property with a var, as in class Polygon; but not the other way around. This is because a val property implicitly defines a get method, and overriding it as a var also declares a set method in the derived class.


open class Shape {

    open val vertexCount: Int = 0

    open fun draw() { /* */ }

    fun fill() { /*  */ }    // fill is final

}


open class Rectangle : Shape {

    override val vertexCount = 4

    override fun draw() { /*   */ }

}


class Square(override val vertexCount: Int = 4) : Rectangle {

    final override fun draw() { /*   */ }

}


class Polygon : Shape {

    override var vertexCount: Int = 0  // can be set later

}

Overriding Rules

Back to Contents


There is a fairly well defined issue in C++ with multiple inheritance (the "diamond" problem), where a class inherits from two different classes which have a common ancestor. To illustrate this, consider the artistic cowboy:

class Person {

public:

   virtual void draw() { /*  */ }

}

class Artist :  public Person {

public:

    void draw() { /*  */ }  // draws a picture

}

class Cowboy : public Person {

public:

    void draw() { /*  */ }  // draws a six-shooter!

}

class ArtisticCowboy : public Artist, public Cowboy {

    // ... 

}

There are actually two issues here: first, because both Artist and Cowboy inherit Person directly, there are actually two different Person entities inside ArtisticCowboy; but this can be solved with virtual inheritance. The more important problem is, what the heck does the ArtisticCowboy do when asked to draw? Use a pattern of bullet holes to make a picture?

Kotlin, like Java, can only extend a single class, but can implement multiple interfaces. Therefore, it's possible to encounter something like the Artistic Cowboy problem. Kotlin's solution is a rule: if a class inherits multiple implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation. It can then call one (or more) parent implementations from its own implementation.

open class Artist {

    open fun draw() { /*   */ }

}

interface Cowboy {

    fun draw() { /*   */ }    // interfaces and their members are open by default 

}

class ArtisticCowboy : Artist, Cowboy  {

    // the compiler requires an override for draw()

    override fun draw() { 

        super<Artist>.draw()

        super<Cowboy>.draw()

    }

}


Constant Definition and Readability

Back to Contents


This topic is covered extensively in my Best Practices in Java article.

Numeric Types and Boxed Types

Back to Contents


In Java, the capitalized versions of numbers (Integer, Double, etc.) are objects, not primitives; and will  automagically be converted between the boxed objects and primitives (sometimes). The important things to remember are:

  1. The boxed objects are immutable; and
  2. Equality operators between two objects will only check whether they are the same object, not whether they have the same value.

In Kotlin, the integer types (Byte, Short, Int, Long) and the floating point types (Double, Float) act like primitives or like objects. For example, above we show an extension for (class) Double. The nullable versions (Int?, Float?, etc.) correspond to Java's Boxed types, and are treated as such in the JVM.

One warning from the Autobox section of the Java article is about the == and != operators. This does not apply to Kotlin, because of Kotlin's operator overloading. In particular, those operators will look for, and call if it exists, a method called .equals() to evaluate the condition.

On the other hand, Kotlin does not have any implicit type widening, so it's impossible to compare an Int? to a Long?. Why? The first thing fun Long.equals(other: Any?) does is check whether other is a Long or Long?. If other is an Int?, the function quickly returns false.

All that said, Kotlin does still provide the ability to check whether two references are pointing to the same object: the operator === provides this referential equality.

    val twoK: Int? = 2000

    val dosK: Int? = 2000

    println(twoK == dosK)     // true

    println(twoK === dosK)   // false

Strings and String Templates

Back to Contents


The String type in Kotlin is similar to String in Java; it is an immutable sequence of UTF-16 characters and surrogate pairs. Just as in Java, it is legal to concatenate a String with any other object with the + operator (as long as the first operand is a String). However, each + creates a new object; while Java builds an implicit StringBuilder in a multi-operand concatenation, Kotlin does no such thing.

Instead, Kotlin provides String templates. A literal string with $identifier will embed the value of the identifier in the string. A literal string with ${expression} will embed the value of the expression in the string.  Example:

val three: Double = 3.0

val string = "$three cubed is ${three.pow(3)"   // → 3.0 cubed is 27.0

There is also a way to create a multi-line string.  See the Kotlin documentation.

Collections

Back to Contents


Kotlin collections are quite similar to collections in Java. The same concepts of Set, List, and Map exist; there are concrete types such as HashSet and ArrayList. But Kotlin has a richer interface hierarchy of mutable collection interfaces and classes; and operator overloading allows some syntactic sugar.

  • The array index operator [] can be used to access list and set elements by index.
  • A map values may be accessed by placing the key inside [].
  • The in operator translates to .contains(): a in b → b.contains[a].

The same empty collection and singleton collection methods exist (singletonList(), emptyMap(), etc.). And the same warning about immutable collections applies, but in Kotlin, one may check whether a map is mutable with the is operator: 

  if (myMap is MutableMap) ... // the map is mutable if it descends from MutableMap

Enumerations

Back to Contents


Enumerations in Kotlin are almost identical to enumerations in Java. All the reasons for using enums in Java still apply to Kotlin

NextHome

Copyright © 2019-2025 Jim Hamilton - All Rights Reserved.

Powered by

This website uses cookies.

We use cookies to analyze website traffic and optimize your website experience. By accepting our use of cookies, your data will be aggregated with all other user data.

DeclineAccept