Comments #

Comments are one of those things in programming that beginners often underestimate and experienced developers often misuse. A good comment isn’t just a translation of code into human language — it explains why something is done, not what is done (that’s already visible from the code itself). Kotlin supports three types of comments: single-line comments, multi-line comments, and KDoc documentation comments. Each has its own proper context, and knowing when to use which is a skill that separates maintainable code from code that becomes a burden on the team.

Single-Line Comments #

Single-line comments start with //. All text after // until the end of the line is completely ignored by the compiler — it doesn’t affect program execution at all.

// This is a standalone single-line comment
val ageLimit = 18 // minimum age to create an account

val price = 50_000
val discount = 0.10
val finalPrice = price * (1 - discount) // result: 45000.0

Single-line comments are most often used in two positions: on their own line before a code block that needs explanation, or at the end of a line as a short note (inline comment).

When Single-Line Comments Are Appropriate #

Use single-line comments to explain things that aren’t immediately visible from the code:

// CORRECT: explaining the reasoning behind a magic number
val TIMEOUT_MS = 5_000 // internal network latency tolerance per SLA

// CORRECT: explaining a non-intuitive edge case
val lastIndex = list.size - 1 // size() returns 1-based, indexes are 0-based

// CORRECT: flagging a part that needs special attention
val hash = md5(password) // TODO: switch to bcrypt before production — see ticket SEC-204

// ANTI-PATTERN: comments that merely repeat the code
val name = "Budi" // set name to Budi
val age = 25      // set age to 25
println(name)     // print name

The last comments above add no information at all. Anyone reading val name = "Budi" already knows what’s happening — it doesn’t need to be rewritten in human language.


Multi-Line Comments #

Multi-line comments open with /* and close with */. All text between them — no matter how many lines — is treated as a comment.

/*
    This algorithm uses a sliding window approach to compute
    a moving average with O(n) complexity, instead of O(n²)
    if using a nested loop. Reference: https://en.wikipedia.org/wiki/Sliding_window_protocol
*/
fun movingAverage(data: List<Double>, window: Int): List<Double> {
    if (data.size < window) return emptyList()
    
    val result = mutableListOf<Double>()
    var sum = data.take(window).sum()
    result.add(sum / window)
    
    for (i in window until data.size) {
        sum += data[i] - data[i - window]
        result.add(sum / window)
    }
    
    return result
}

Nested Multi-Line Comments #

One of Kotlin’s advantages over Java: multi-line comments can be nested. In Java, placing /* ... */ inside /* ... */ is a compilation error. In Kotlin, it’s valid.

/*
    This function handles three different scenarios:
    
    /* Scenario 1: empty data */
    If the list is empty, return emptyList() immediately
    
    /* Scenario 2: window larger than the data */
    Can't compute an average  return emptyList()
    
    /* Scenario 3: normal */
    Compute with a sliding window
*/
fun processData(data: List<Double>, window: Int): List<Double> {
    // implementation...
    return emptyList()
}

This is especially useful when you need to comment out a block of code that already contains multi-line comments inside it.

Temporarily Disabling Code #

One very practical use of multi-line comments is temporarily disabling a block of code during debugging or experiments, without deleting it.

fun calculateTotal(items: List<Int>): Int {
    /*
    // Old implementation — too slow for large datasets
    var total = 0
    for (item in items) {
        total += item
    }
    return total
    */
    
    // New implementation with reduce
    return items.reduce { acc, item -> acc + item }
}
Don’t leave commented-out code in a production codebase for a long time. If old code is still relevant as a reference, keep it in version control (Git history) — not as a comment. Commented-out code adds noise and confuses other developers.

Documentation Comments — KDoc #

KDoc is Kotlin’s official documentation system, equivalent to Javadoc in Java. KDoc comments start with /** and end with */. Each line inside is usually prefixed with * (space, asterisk, space), although this isn’t strictly required.

KDoc isn’t just an ordinary comment — it’s read by tools like Dokka to generate automatic HTML documentation, and displayed directly in IntelliJ IDEA when you hover over a function or class.

/**
 * Calculates compound interest based on initial principal, interest rate, and duration.
 *
 * Formula used: A = P(1 + r/n)^(nt)
 * where P is the initial principal, r is the annual interest rate (decimal),
 * n is the compounding frequency per year, and t is the duration in years.
 *
 * @param principal Initial principal in Rupiah
 * @param interestRate Annual interest rate as a decimal (e.g., 0.05 for 5%)
 * @param years Investment duration in years
 * @param frequency Compounding frequency per year (default: 12 for monthly)
 * @return Total investment value after the specified period
 */
fun compoundInterest(
    principal: Double,
    interestRate: Double,
    years: Int,
    frequency: Int = 12
): Double {
    return principal * Math.pow(1 + interestRate / frequency, (frequency * years).toDouble())
}

Available KDoc Tags #

KDoc supports various tags for documenting different aspects of a declaration:

TagPurposeExample
@param nameDocuments a function parameter@param id User's unique ID
@returnExplains the returned value@return null if not found
@throws ExceptionClassDocuments exceptions that may be thrown@throws IOException if the file can't be read
@property nameDocuments a property in a data class@property name User's full name
@constructorDocuments the primary constructor@constructor Creates an instance with initial configuration
@seeReference to another declaration or URL@see User
@sinceVersion since which this feature is available@since 1.2.0
@suppressHides certain IDE warnings@suppress("UNCHECKED_CAST")
@sampleIncludes sample code from elsewhere@sample example.UserExample.createUser

KDoc for Classes #

/**
 * Represents a user in the system.
 *
 * This class is a data class used as the main model
 * for user CRUD operations. Every user has a unique ID
 * generated at creation time that can't be changed.
 *
 * @property id Unique user ID — automatically generated by the database
 * @property name User's full name, at least 2 characters
 * @property email Verified email address, must be unique across the system
 * @property role User's role in the system, default [Role.USER]
 * @property active Account active status, false means the account is suspended
 *
 * @see UserRepository
 * @see Role
 * @since 1.0.0
 */
data class User(
    val id: Long,
    val name: String,
    val email: String,
    val role: Role = Role.USER,
    val active: Boolean = true
)

/**
 * Roles available in the authorization system.
 *
 * @property ADMIN Full access to all features and user management
 * @property MODERATOR Can manage content but can't modify other users
 * @property USER Standard access to features available to the public
 */
enum class Role {
    ADMIN, MODERATOR, USER
}

KDoc for Interfaces #

/**
 * Contract for all repositories that manage user data.
 *
 * Implementations of this interface must be thread-safe because they
 * can be called from coroutines running on different threads concurrently.
 *
 * @see User
 * @see UserRepositoryImpl
 */
interface UserRepository {
    
    /**
     * Finds a user by their ID.
     *
     * @param id The user ID to look up
     * @return A [User] object if found, null if it doesn't exist
     */
    suspend fun findById(id: Long): User?
    
    /**
     * Finds a user by their email address.
     *
     * The search is case-insensitive — "[email protected]" and
     * "[email protected]" are considered the same email.
     *
     * @param email The email address to look up
     * @return A [User] object if found, null if it doesn't exist
     */
    suspend fun findByEmail(email: String): User?
    
    /**
     * Saves a new user to the database.
     *
     * @param user The user data to save. The [User.id] field
     *             is ignored — the ID is generated by the database.
     * @return The [User] object complete with its newly generated ID
     * @throws IllegalArgumentException if the email is already registered
     * @throws IllegalArgumentException if the name is shorter than 2 characters
     */
    suspend fun save(user: User): User
    
    /**
     * Permanently deletes a user from the database.
     *
     * This operation can't be undone. All user-related data
     * (profile, transaction history, etc.) is also deleted because
     * of the foreign key constraint with CASCADE DELETE.
     *
     * @param id The user ID to delete
     * @return true if the user was successfully deleted, false if the ID wasn't found
     */
    suspend fun delete(id: Long): Boolean
}

Text Formatting in KDoc #

KDoc supports a subset of Markdown for formatting description text:

/**
 * Validates password strength based on the following criteria:
 *
 * - At least **8 characters**
 * - Contains at least one **uppercase letter**
 * - Contains at least one **number**
 * - Contains at least one **special character** (`!@#$%^&*`)
 *
 * Usage example:
 * ```kotlin
 * val strong = validatePassword("P@ssw0rd!")  // true
 * val weak = validatePassword("password")  // false
 * ```
 *
 * > **Note:** This function only validates the format, it doesn't check
 * > whether the password has ever leaked in a credential leak database.
 *
 * @param password The password to validate
 * @return true if it meets all criteria, false if not
 * @see [HaveIBeenPwned](https://haveibeenpwned.com/API/v3) for credential leak checks
 */
fun validatePassword(password: String): Boolean {
    val uppercase = Regex("[A-Z]")
    val digit = Regex("[0-9]")
    val specialChar = Regex("[!@#\\$%^&*]")
    
    return password.length >= 8 &&
        uppercase.containsMatchIn(password) &&
        digit.containsMatchIn(password) &&
        specialChar.containsMatchIn(password)
}

Cross-References in KDoc #

You can reference other classes, functions, or properties with the [DeclarationName] notation:

/**
 * Sends a notification to all users with the [Role.ADMIN] role.
 *
 * This function calls [sendEmail] internally for each admin.
 * Make sure the SMTP configuration is correct in [EmailConfig] before
 * calling this function.
 *
 * @param message The notification content to send
 * @throws [EmailException] if the SMTP connection fails
 */
fun notifyAdmins(message: String) {
    // implementation...
}

Generating Documentation with Dokka #

Dokka is the official tool for generating HTML documentation from KDoc. Here’s how to use it in a Gradle project:

Add the plugin to build.gradle.kts:

plugins {
    kotlin("jvm") version "2.0.0"
    id("org.jetbrains.dokka") version "1.9.20"
}

Generate documentation:

./gradlew dokkaHtml

The result is a complete HTML site at build/dokka/html/ that can be hosted anywhere — GitHub Pages, an internal server, or wherever you like.

flowchart LR
    A[".kt File\n(with KDoc)"] --> B["Dokka\nProcessor"]
    B --> C["HTML\nbuild/dokka/html/"]
    B --> D["Markdown\nbuild/dokka/gfm/"]
    B --> E["Javadoc format\nbuild/dokka/javadoc/"]
    C --> F["GitHub Pages /\nInternal Server"]

The Philosophy of Good Comments #

Understanding comment syntax is only half the story. The other half is knowing when to write comments and what deserves a comment.

Good Code Is the Best Documentation #

Descriptive variable, function, and class names drastically reduce the need for comments.

// ANTI-PATTERN: non-descriptive names, needs comments to explain
// Calculate d based on the business formula
fun calc(p: Double, r: Double, t: Int): Double {
    return p * Math.pow(1 + r, t.toDouble())
}

// CORRECT: descriptive names — no comments needed
fun calculateFinalInvestmentValue(initialCapital: Double, annualInterestRate: Double, years: Int): Double {
    return initialCapital * Math.pow(1 + annualInterestRate, years.toDouble())
}

Explain the “Why”, Not the “What” #

// ANTI-PATTERN: explaining what's already obvious
val pageLimit = 20 // set page limit to 20

// CORRECT: explaining why this number was chosen
val pageLimit = 20 // calibrated based on median mobile user scroll time — see UX research Q3

// ANTI-PATTERN: explaining what's already visible from the code
// Loop from 0 to the length of the list
for (i in 0 until list.size) { ... }

// CORRECT: no comment needed at all if the code is already clear
for (item in list) { ... }

Comments as Danger Signs #

If you feel the need for a long comment to explain a piece of code, that’s often a signal the code needs to be refactored.

// ANTI-PATTERN: a long comment hiding complex code
// This function takes data from the cache if available,
// if not, takes it from the database, stores it in the cache,
// then returns the result, but if the database also errors,
// tries to fall back to a local file, and if everything fails returns null
fun fetchData(id: String): Data? {
    // ... 80 lines of complex code ...
}

// CORRECT: extract into small functions whose names speak for themselves
fun fetchData(id: String): Data? {
    return fetchFromCache(id)
        ?: fetchFromDatabaseAndCache(id)
        ?: fetchFromLocalFallback(id)
}

TODO and FIXME #

Kotlin (and IntelliJ IDEA) recognizes comments with special prefixes as task markers:

// TODO: add input validation before calling the external API
fun sendData(payload: String) { ... }

// FIXME: there's a race condition here when two threads call this simultaneously
fun updateCounter() { ... }

// HACK: workaround for a bug in the third-party library version 2.3.1
// Remove this after they release a fix in version 2.4.0
val result = library.processData(input.trim() + "\n")

// NOTE: this function deliberately doesn't use coroutines because
// it needs to block until finished — the caller context expects a synchronous value
fun fetchConfig(): Config { ... }

IntelliJ IDEA displays all TODO and FIXME markers in a dedicated panel (View → Tool Windows → TODO), so you can track all pending work without manually searching the entire codebase.


Comments in Various Contexts #

Comments in File Headers #

For files containing utility functions or constants, a comment at the top of the file is useful for explaining the file’s overall purpose.

/*
 * Utilities for cryptographic operations used across the application.
 *
 * All functions in this file use algorithms that have been security-audited.
 * DO NOT add custom cryptography implementations — use what's already here.
 *
 * Author: Security Team
 * Last updated: 2024-03
 */

package com.myapp.util.crypto

import javax.crypto.Cipher
// ...

Comments in Complex Blocks #

fun compressData(data: ByteArray): ByteArray {
    val output = ByteArrayOutputStream()
    
    // GZIPOutputStream must be flushed and closed explicitly
    // before taking the bytes from ByteArrayOutputStream.
    // Calling output.toByteArray() before gzip.close() produces
    // corrupted data — this is non-intuitive Java IO behavior.
    GZIPOutputStream(output).use { gzip ->
        gzip.write(data)
    }
    
    return output.toByteArray()
}

Comments for Regex #

Regex is one context where comments are almost always needed because its syntax isn’t easy to read.

// Format: +62-XXX-XXXX-XXXX or 08XX-XXXX-XXXX
// Supports spaces, dashes, or no separator
val REGEX_ID_PHONE_NUMBER = Regex(
    """^(\+62|0)[0-9]{2,3}[-\\s]?[0-9]{3,4}[-\\s]?[0-9]{4}$"""
)

// Standard email format — doesn't support quoted strings or IP literals
// because our use case doesn't require them
val REGEX_EMAIL = Regex("""^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$""")

Summary #

  • Three types of comments// for single-line, /* */ for multi-line, /** */ for KDoc. Each has its proper context.
  • Kotlin multi-line comments can be nested — unlike Java, /* /* */ */ is valid in Kotlin. This is useful when commenting out code that already contains comments.
  • KDoc is the official documentation standard — use /** */ with tags like @param, @return, @throws, and others to document public APIs. It’s read by IDEs and the Dokka tool.
  • Explain the “why”, not the “what” — the best comments explain intent, reasoning, or context that isn’t visible from the code itself. Expressive code doesn’t need comments that merely translate it.
  • Clean code reduces the need for comments — descriptive names for variables, functions, and classes are the best documentation. If you need a long comment to explain one function, consider refactoring it.
  • Remove commented-out code — keep it in Git history, not in the active codebase. Dead-code comments add noise and confuse.
  • Use TODO and FIXME consistently — IntelliJ IDEA and most CI tools can track them automatically, so no task gets forgotten.
  • Dokka for automatic documentation — with the Dokka plugin and good KDoc, you can generate a complete HTML documentation site with a single Gradle command.

← Previous: Main Syntax   Next: Variables →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact