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:
| Tag | Purpose | Example |
|---|---|---|
@param name | Documents a function parameter | @param id User's unique ID |
@return | Explains the returned value | @return null if not found |
@throws ExceptionClass | Documents exceptions that may be thrown | @throws IOException if the file can't be read |
@property name | Documents a property in a data class | @property name User's full name |
@constructor | Documents the primary constructor | @constructor Creates an instance with initial configuration |
@see | Reference to another declaration or URL | @see User |
@since | Version since which this feature is available | @since 1.2.0 |
@suppress | Hides certain IDE warnings | @suppress("UNCHECKED_CAST") |
@sample | Includes 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.