skip to content

Compare `internal` and `private` for top-level declarations. What exactly is the scope of each?

level: middleimportance: must knowfreq 50%

answer

  1. private top-level = same FILE
  2. internal = same MODULE (Gradle source set / IDEA module)
  3. Order: private < internal < public
  4. internal enforced via JVM name mangling
  5. internal is encapsulation, not security

basics

~10 s

internal means visible to all code in the same module (your compiled unit). private on a top-level declaration means visible only inside the same file. Both hide the declaration from outside consumers.

solid answer

~40 s

For **top-level** declarations, `private` scopes visibility to the **same file** — other files in the same package or module cannot see it. `internal` scopes to the whole **module**: every file compiled together (a Gradle source set / IntelliJ module / Maven build) can use it, but code in other modules cannot, even if it depends on yours. So `private` < `internal` < `public` in breadth. The compiler enforces `internal` by name-mangling the JVM bytecode (appending a module hash to the method name) so other modules can't bind to it accidentally. A common use: expose `internal` helpers across a module's files while keeping `public` API minimal, and use top-level `private` for single-file implementation details.

code

kotlin · 9 lines
kotlin
// File A.kt
internal fun shared() = 42   // visible across the module
private fun onlyHere() = 1   // visible only in A.kt

// File B.kt (same module)
fun use() {
    shared()      // OK: internal, same module
    // onlyHere() // ERROR: private to A.kt
}

go deeper

for a junior

Knows private hides things and internal is broader, even if fuzzy on file-vs-module.

for a middle

Pins private to the file and internal to the module; gives the correct ordering and a use case for each.

for a senior

Explains JVM name mangling for internal, the precise definition of a module (source sets), and that it's encapsulation not security.

for a principal

Reasons about module decomposition, library API hygiene, and how internal plus explicit-API mode shape a maintainable public surface across modules.

## Scope definitions (top-level) - **`private`**: visible only within the **same source file**. Two different `.kt` files in the same package cannot see each other's top-level `private` declarations. - **`internal`**: visible within the **same module**. A *module* is a set of files compiled together — a Gradle source set (e.g. `main`), an IntelliJ module, a Maven project, or one Ant/CLI invocation. All files in that module can access an `internal` declaration; consumers in other modules cannot. - **`public`** (default): visible everywhere the file is on the classpath. ## Ordering ``` private (file) < internal (module) < public (everywhere) ``` `protected` doesn't apply to top-level declarations (it's class-members only) and is unrelated to this ordering for top-levels. ## How `internal` is enforced on the JVM The JVM has no native "module" access level, so the Kotlin compiler **mangles names**: an `internal` function compiles to a JVM method whose name carries a module-specific suffix (e.g. `helper$my_module_main`). This prevents code in another module — including Java callers — from binding to it by accident. It also means `internal` members are technically reachable via reflection or from Java with the mangled name, so it's an *encapsulation* tool, not a *security* boundary. ```kotlin // file: Repo.kt (module :data) internal fun mapRow(row: Row): User = User(row.id) // any file in :data sees it private fun debugDump(u: User) = println(u) // only Repo.kt sees it ``` ## Choosing between them - Use **`private`** top-level for a helper used by only one file — minimal surface, no name pollution. - Use **`internal`** to share across files of a module while hiding from downstream consumers (e.g. a library's implementation package). - Promote to **`public`** only what is genuinely part of the API. ## Common gotcha Developers expect top-level `private` to mean "this package." It does **not** — it's strictly the file. If you need cross-file-but-not-cross-module sharing, that's exactly `internal`.

  • Exactly what counts as a 'module' for `internal`?
    A set of files compiled together: a Gradle/Maven source set or build, an IntelliJ module, or one kotlinc/CLI invocation. Test source sets can be a separate module from main.
  • Can Java code in another module call a Kotlin `internal` function?
    Only by using the mangled name; it won't bind by the plain name. So `internal` is enforced encapsulation, not an absolute access lock — reflection/mangled names can still reach it.

saying these in an interview costs you the question

  • Saying top-level `private` means package-scope
  • Claiming `internal` means package-scope
  • Thinking `internal` is a hard security boundary
  • Not knowing what a 'module' means for `internal`
  • Ordering the visibilities incorrectly

context