ProgrammingAndroid Developer

How do visibility modifiers (internal, private, protected, public) work in Kotlin? How do the modifiers differ in various contexts — for classes, functions, properties, objects, top-level functions, and files?

Pass interviews with Hintsage AI assistant

Answer

In Kotlin, there are the following visibility modifiers:

  • public (default): the element is visible everywhere.
  • internal: the element is visible within a single module (jar/gradle module, etc.).
  • protected: visible only within the class and its subclasses.
  • private: visible only within the file or class.

Features:

  • For top-level functions, properties, and classes: private restricts access to the file scope, internal — to the module, while public/protected make no sense outside of a class.
  • For class members: private — only within that class, protected — plus subclasses, internal and public — as described above.
  • Inside an object/companion object: similar to a class.
class MyClass { private val secret = "hidden" protected val id = 42 internal fun foo() {} public fun bar() {} } internal fun moduleFunc() {} private fun fileOnlyFunc() {}

Trick Question

"Can a top-level function be protected? If yes — how does it work? If no — why?"

Answer: No, a top-level function cannot be protected because there is no class it belongs to for that access level. The compiler enforces this — a compilation error will occur.

protected fun magic() {} // Error: protected modifier is not allowed for top-level functions

Examples of Real Errors Due to Ignorance of the Nuances


Story

In a fintech app, it was forgotten that the internal modifier grants access to all elements of a module. As a result, when refactoring, part of the logic was moved to another gradle module, causing data access to stop working, but developers did not notice it immediately, as there were no compilation errors in the old tests.


Story

In a multiplatform project, confidential data was defined as private properties in a companion object. It turned out that this data is serialized and becomes accessible via reflection, because they were declared as val without using annotations to limit export.


Story

At the start of a mobile project, private was used for top-level functions, thinking this would limit access to partner classes. However, in the shared file with utils, these functions were visible to everyone, which led to a risk of information leakage and unforeseen use in business logic.