Kotlin Naming Conventions
Kotlin’s compiler does not enforce naming rules the way it enforces types or null-safety — a class named in lowercase or a constant named in camelCase will still compile without a single error. Naming in Kotlin is instead governed by a widely-adopted style guide (the official Kotlin coding conventions), reinforced by tooling such as IntelliJ IDEA’s built-in inspections, ktlint, and detekt that flag violations before they ever reach review. Learning these conventions well is not cosmetic: a name like MAX_RETRIES versus maxRetries tells a reader, before they’ve even checked the type, whether a value is a true compile-time constant or an ordinary mutable property. This lesson covers every category of Kotlin identifier, explains why each convention exists, and walks through the mistakes that trip up even experienced developers arriving from Java.
Overview: How Kotlin’s Naming System Works
Kotlin groups identifiers into a small number of categories, and each has its own casing rule. Package names are always all lowercase, dot-separated, with no underscores or camelCase — mirroring Java’s reversed-domain convention (com.example.myapp) so that names never collide across case-insensitive file systems.
Classes, interfaces, objects, enum classes, annotation classes, and type aliases use UpperCamelCase (also called PascalCase): every word starts with a capital letter and there are no underscores — class UserAccount, interface Repository, object DatabaseConnection, enum class OrderStatus. This matches Java’s class-naming rule exactly, which keeps interop clean in both directions.
Functions and properties — whether top-level, members of a class, local variables, or parameters — use lowerCamelCase: the first word is lowercase, every following word is capitalized, no underscores. Functions are usually named with verbs (calculateTotal(), toList()) or, for boolean-returning functions and properties, an is/has prefix (isValid, hasChildren). Properties are usually named with nouns (userName, accountBalance).
Constants are where Kotlin’s rules differ meaningfully from casual habit. A "constant" in Kotlin’s precise sense is a value declared with const val, which is only legal at the top level of a file or inside an object/companion object, and whose value must be knowable at compile time (a literal String or primitive). These use SCREAMING_SNAKE_CASE: all capitals, words separated by underscores, e.g. const val MAX_BALANCE = 10_000.0. Crucially, an ordinary val — even one that is never reassigned in practice — is not a compiler-recognized constant, and should keep normal lowerCamelCase, not shout in all caps. This is one of the most common naming mix-ups for developers coming from languages that treat every "final" value the same way.
Backing properties follow a specific idiom: when a class needs to expose a read-only view of an internally mutable value, the private, mutable field gets a leading underscore, and the public, read-only property reuses the same name without it — private val _items paired with val items.
Generic type parameters use a single uppercase letter by convention: T for a generic Type, E for a collection Element, K and V for map Key/Value pairs, R for a transformed Return type. Test function names are a Kotlin-specific relaxation: because Kotlin allows arbitrary text inside backticks, test functions are almost always written as full, readable sentences, e.g. fun `returns null when the list is empty`().
Finally, acronyms inside identifiers are treated as ordinary words: capitalize only the first letter, so HttpClient and XmlParser, not HTTPClient or XMLParser. Since none of this is compiler-checked, the payoff comes entirely from consistency: a reader who has internalized these rules can infer a huge amount about a value — mutability, scope, whether it’s inlined — from its name alone, before reading a single line of implementation.
Syntax
There’s no special syntax to naming itself — it’s a convention applied to ordinary declarations. The table below is the reference to keep nearby.
| Element | Convention | Example |
|---|---|---|
| Package | all lowercase, dot-separated, no underscores | com.example.myapp |
| Class / Interface / Object / Enum class / Type alias | UpperCamelCase (PascalCase) | class UserAccount |
| Function / Property / Local variable / Parameter | lowerCamelCase | fun calculateTotal() |
Compile-time constant (const val) |
SCREAMING_SNAKE_CASE | const val MAX_SIZE = 100 |
| Backing property | leading underscore, private | private val _items |
| Generic type parameter | single uppercase letter | <T>, <K, V> |
| Test function name (optional) | backtick-quoted sentence | fun `handles empty input`() |
Examples
The first example contrasts a class (PascalCase), its properties and a function (camelCase), and a true compile-time constant (SCREAMING_SNAKE_CASE) declared inside a companion object.
class UserAccount(val userName: String, val accountBalance: Double) {
companion object {
const val MAX_BALANCE = 10_000.0
}
}
fun printAccountSummary(account: UserAccount) {
println("User: ${account.userName}, Balance: ${account.accountBalance}")
}
fun main() {
val account = UserAccount("alice92", 250.75)
printAccountSummary(account)
println("Max allowed balance: ${UserAccount.MAX_BALANCE}")
}
Output:
User: alice92, Balance: 250.75
Max allowed balance: 10000.0
UserAccount is PascalCase because it’s a class; userName and accountBalance are camelCase properties; MAX_BALANCE is SCREAMING_SNAKE_CASE because it’s declared with const val and is genuinely fixed at compile time. Note it’s accessed as UserAccount.MAX_BALANCE, through the class itself, not through an instance.
The second example shows the backing-property naming idiom, where a private mutable field is exposed as a public read-only property.
class ShoppingCart {
private val _items = mutableListOf<String>()
val items: List<String>
get() = _items
fun addItem(name: String) {
_items.add(name)
}
}
fun main() {
val cart = ShoppingCart()
cart.addItem("Keyboard")
cart.addItem("Mouse")
println(cart.items)
}
Output:
[Keyboard, Mouse]
_items is the real, mutable storage, kept private so outside code cannot call .add() or .remove() on it directly. The public items property has no storage of its own — its custom get() just returns the same list reference, but typed as the read-only List<String> interface, so callers see an immutable-looking view even though the underlying object is mutable.
The third example names enum constants in SCREAMING_SNAKE_CASE (the traditional, safest choice for simple enums) and pairs them with an exhaustive when expression.
enum class OrderStatus {
PENDING,
SHIPPED,
DELIVERED,
CANCELLED
}
fun describeStatus(status: OrderStatus): String = when (status) {
OrderStatus.PENDING -> "Order received, not yet shipped"
OrderStatus.SHIPPED -> "On the way"
OrderStatus.DELIVERED -> "Delivered to customer"
OrderStatus.CANCELLED -> "Order was cancelled"
}
fun main() {
val status = OrderStatus.SHIPPED
println(describeStatus(status))
}
Output:
On the way
OrderStatus is PascalCase because it’s an enum class; its constants are SCREAMING_SNAKE_CASE. Because describeStatus returns the value of the when expression, the compiler requires every OrderStatus entry to be handled — if a new status were added to the enum without updating this function, the code would stop compiling until it was.
How It Works Step by Step: Naming and the Compiled Bytecode
These conventions aren’t arbitrary decoration — several of them exist precisely because different kinds of declarations compile to different bytecode. A const val declared at the top level or inside an object/companion object becomes a static final field on the JVM, and because its value is known at compile time, the Kotlin compiler inlines that literal value directly into every call site that reads it. That has a real consequence: if MAX_BALANCE lives in a separately compiled library and you bump its value, any module that hasn’t been recompiled keeps using the old, baked-in number. SCREAMING_SNAKE_CASE is a visual flag for exactly this behavior.
An ordinary val, even one whose value never changes in practice, compiles very differently: it becomes a private backing field plus a public getter method, and every read goes through that getter call rather than being inlined. Recompiling the class that declares it is enough to propagate a change everywhere. That’s why plain val properties stay in camelCase — naming communicates which compilation strategy, and which recompilation guarantees, apply.
The backing-property pattern from the ShoppingCart example works the same way at the bytecode level: _items is one real MutableList object living on the heap; items is not separate storage but a method (getItems() from the JVM’s point of view) that returns that same reference, upcast to the narrower List type. Encapsulation here comes from the type system, not from copying data on every access.
Common Mistakes
Mistake 1: Naming a mutable value like a constant. SCREAMING_SNAKE_CASE should be reserved strictly for const val. Slapping it on a var is misleading — it compiles fine, but it lies to every reader about mutability.
class RetryPolicy {
var MAX_RETRIES = 3
var timeoutMs = 500
}
Anyone reading MAX_RETRIES will assume it’s fixed and safe to inline mentally, yet it’s a plain mutable field that can change at runtime. Either make it a genuine constant, or name it like the ordinary property it is:
class RetryPolicy {
companion object {
const val MAX_RETRIES = 3
}
var timeoutMs = 500
}
Mistake 2: Hungarian notation and type prefixes. Prefixing names with their type (strName, iCount) was common in older, weakly-typed or dynamically-typed codebases, but it’s redundant in Kotlin: the type is always declared (or inferred and shown by the IDE), so the prefix just adds noise and gets stale the moment the type changes.
val strName: String = "Nadia"
val iCount: Int = 5
println("$strName is $iCount years into her Kotlin journey")
Output:
Nadia is 5 years into her Kotlin journey
It compiles and runs identically, but the names carry no meaning beyond their type. Drop the prefixes and let the name describe the value’s role:
val name: String = "Nadia"
val count: Int = 5
println("$name is $count years into her Kotlin journey")
Output:
Nadia is 5 years into her Kotlin journey
Mistake 3: Shouting acronyms in identifiers. Capitalizing an entire acronym inside a PascalCase or camelCase name breaks word boundaries and makes names harder to scan, especially when two acronyms sit next to each other.
class HTTPURLConnectionManager {
fun OPENConnection() {
}
}
HTTPURLConnectionManager reads as one long block of capitals with no visual word breaks, and OPENConnection mixes a shouted acronym-like prefix into what should be an ordinary camelCase function name. Treat every acronym as a normal word instead:
class HttpUrlConnectionManager {
fun openConnection() {
}
}
Best Practices
- Reserve SCREAMING_SNAKE_CASE strictly for
const val; an ordinaryvarorvalshould always be camelCase, no matter how fixed it feels in practice. - Prefix backing properties with a single leading underscore (
_items) and never use a trailing or double underscore. - Treat acronyms as ordinary words in identifiers:
HttpClient, notHTTPClient;toUrl(), nottoURL(). - Keep package names all lowercase with no underscores or camelCase, matching your reversed domain (
com.yourcompany.module). - Prefer descriptive names over abbreviations for anything with a lifespan longer than a few lines; short names like
i,j, orxare fine only in tight, obvious loop scopes. - Write test function names as full, backtick-quoted sentences — readability beats Java-style method-name conventions in test code.
- Let an IDE inspection or a linter such as
ktlint/detektcatch naming drift automatically instead of relying purely on manual review.
Practice Exercises
- Rewrite these three declarations using idiomatic Kotlin naming, and explain what’s inconsistent about each one:
var Score: Int = 0,val PLAYER_NAME = "Rae",const val maxLives = 3. - Write a class
ProductCatalogthat stores product names in an internal, private, mutable list, and exposes them publicly as a read-only property, following the backing-property naming convention from this lesson. Add two products and print the public property; expect[Notebook, Pen]if those are the two names you add. - Define an enum class for traffic-light colors and an exhaustive
whenexpression that maps each color to an action string ("Stop", "Caution", "Go"), naming every declaration according to the conventions covered here.
Summary
- Kotlin naming rules aren’t compiler-enforced, but they’re the shared idiom that keeps code readable and tool-friendly across the whole ecosystem.
- Packages are all lowercase with no underscores; classes, interfaces, objects, and enums use UpperCamelCase; functions, properties, and variables use lowerCamelCase.
- SCREAMING_SNAKE_CASE is reserved specifically for
const val— a true, inlined compile-time constant — never for an ordinaryvarorval. - Backing properties pair a leading-underscore private mutable field with a public read-only property of the same name minus the underscore.
- Generic type parameters use single capital letters (
T,R,E,K,V); test functions may use backtick-quoted, full-sentence names. - Treat acronyms as ordinary words in identifiers (
Http,Url,Xml) rather than shouting them in all caps. - Let
ktlint/detektand your IDE’s inspections enforce these rules automatically so code review time goes to logic, not style.
