AlgoGalaxy

AlgoGalaxy

University
CodeRush
Jobs
Tools
Insights
Explore
Personalize
JavaGoPHPRustGraphsTreesArraysDynamic ProgrammingGreedyHashingHeapLinked ListNumber TheoryStackStringsTrieUnion FindBacktrackingBinary SearchBit Manipulation

Kotlin Constructors, Properties, and init

Primary constructors place required state next to a class name. Use init to protect invariants at the boundary, defaults for truly optional values, and a named factory when a caller should understand a special creation policy. The goal is simple: invalid objects should be difficult or impossible to create.

Work through the programs in order. Each example changes one idea at a time, includes the expected result, and asks you to make a small change. That practice turns object-oriented vocabulary into code you can confidently write, review, and test.

What You Will Build

This is a code lab, not a compressed definition. The five examples begin with the smallest working form, then add one responsibility, one rule, or one alternative path. Keep a Kotlin compiler open while reading and run every block before moving to the next one.

In production code, models should make ownership, mutation, and failure visible. Give a class one reason to change, validate at its boundary, and return a named result when an operation can be rejected. Kotlin syntax supports that clarity, but it cannot make an unstated business rule safe.

Example 1: Declare constructor properties

val in the primary constructor creates a readable property. The object cannot later replace that reference, which makes the basic model easier to reason about.

Kotlin

class Product(
    val name: String,
    val price: Int,
)

fun main() {
    val product = Product("Notebook", 60)
    println("${product.name}: ${product.price}")
}

Expected Result

Notebook: 60

Try next: Create a second product and check that changing one local variable does not change the other object.

Trace the program from construction through its printed result. Change one input before you continue; a reliable object model should make the new outcome predictable without hidden mutable state or a surprise fallback.

Example 2: Validate in init

init runs after the primary constructor receives its values. require stops invalid state at construction rather than forcing every later method to re-check it.

Kotlin

class Percentage(value: Int) {
    val value: Int

    init {
        require(value in 0..100) { "percentage must be 0 through 100" }
        this.value = value
    }
}

fun main() {
    println(Percentage(85).value)
}

Expected Result

85

Try next: Try 101 and read the failure. Decide whether the caller should validate first or handle this construction error.

Trace the program from construction through its printed result. Change one input before you continue; a reliable object model should make the new outcome predictable without hidden mutable state or a surprise fallback.

Example 3: Use defaults for optional state

A default belongs in the constructor only when it is safe and meaningful for every caller. Named arguments make the changed value obvious at the call site.

Kotlin

class Ticket(
    val title: String,
    val priority: String = "normal",
)

fun main() {
    val ticket = Ticket(title = "Fix login", priority = "high")
    println("${ticket.title}: ${ticket.priority}")
}

Expected Result

Fix login: high

Try next: Create a Ticket without priority and verify the default is visible and intentional.

Trace the program from construction through its printed result. Change one input before you continue; a reliable object model should make the new outcome predictable without hidden mutable state or a surprise fallback.

Example 4: Expose read-only state with a controlled update

A public getter and private setter let callers observe the state while only class operations can change it. This is useful when an update needs a named business rule.

Kotlin

class Thermometer(celsius: Int) {
    var celsius = celsius
        private set

    fun warmBy(amount: Int) {
        celsius += amount
    }
}

fun main() {
    val meter = Thermometer(20)
    meter.warmBy(3)
    println(meter.celsius)
}

Expected Result

23

Try next: Add a range check in warmBy if your domain has a safe operating range.

Trace the program from construction through its printed result. Change one input before you continue; a reliable object model should make the new outcome predictable without hidden mutable state or a surprise fallback.

Example 5: Name a creation policy with a factory

A factory can protect both defaults and validation. The caller reads business intent instead of guessing what an unexplained constructor argument means.

Kotlin

class Subscription private constructor(
    val plan: String,
    val months: Int,
) {
    companion object {
        fun trial(): Subscription = Subscription("trial", 1)
    }
}

fun main() {
    val subscription = Subscription.trial()
    println("${subscription.plan}: ${subscription.months}")
}

Expected Result

trial: 1

Try next: Create a paid factory only after deciding the valid plan names and allowed month range.

Trace the program from construction through its printed result. Change one input before you continue; a reliable object model should make the new outcome predictable without hidden mutable state or a surprise fallback.

Example 6: Make the input visible

Start by naming the value the next line depends on. This keeps the lesson input explicit before we apply a transformation or call an external boundary. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Replace the sample value with one from your own notes, predict the result, and then run it.

Example 7: Guard an empty boundary

A small guard makes the allowed and rejected path visible. We want a caller to receive an intentional result instead of discovering an unexplained failure later. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Try an empty value as well as the normal value and describe why the two outcomes differ.

Example 8: Keep the transformation pure

This step returns a value instead of mixing the rule with display or storage work. That separation gives us a small unit we can run, test, and reuse. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Call the same rule twice with different inputs and confirm that each result depends only on its input.

Example 9: Make a branch deliberate

Use a named branch where the business rule changes. A future reader should be able to point at the condition and state the result for both sides. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Change the boundary value by one and explain exactly which branch becomes active.

Example 10: Use a collection without hidden mutation

Collections become safer when we can see which value is read, which value is derived, and whether the original collection remains owned by the caller. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Add one more item and predict the derived result before executing the code.

Example 11: Return a named outcome

A named outcome is easier to test than an implied side effect. We can now distinguish a successful path from a rejected path without guessing from console noise. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Rename the outcome in your own words while preserving the same success and failure rule.

Example 12: Trace a small loop

Work through one value at a time. The point is not to memorize syntax; it is to see how the accumulator or output changes after each repeatable step. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Use a shorter collection first, then add one value and trace the extra iteration aloud.

Example 13: Keep display at the edge

The core rule prepares a value, while the outer line decides how to show it. That keeps the result usable by a UI, test, API, or command-line caller. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Change only the display text and verify that the underlying rule and result stay unchanged.

Example 14: Test the closest boundary

A useful example includes the nearest boundary, not only the happy path. We are making the behavior inspectable before a real caller relies on it. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Try the lowest supported value and then one value just outside the accepted range.

Example 15: Write a tiny regression check

A compact assertion turns the lesson into a repeatable promise. It helps us catch a changed rule before a larger program hides the source of the mistake. We are applying that habit while revisiting Kotlin Constructors, Properties, and init.

Keep the example small enough to trace. First predict the normal result; then change one input and explain the boundary result in your own words.

Kotlin

fun review(value: String?): String =
    value?.trim()?.takeIf { it.isNotEmpty() }?.uppercase()
        ?: "needs a value"

fun main() {
    println(review("Kotlin Constructors Properties and init"))
}

Expected Result

KOTLIN CONSTRUCTORS PROPERTIES AND INIT

Try next: Deliberately change the expected value once, observe the failed check, and then restore the correct contract.

Practice Checklist

Reproduce all five programs from memory before moving forward. Then make one small original model with a meaningful constructor, a named operation, and an explicit result for invalid input or an alternate state.

  • Practice 1: recreate “Declare constructor properties” without copying, change one value, and explain the result before running it.
  • Practice 2: recreate “Validate in init” without copying, change one value, and explain the result before running it.
  • Practice 3: recreate “Use defaults for optional state” without copying, change one value, and explain the result before running it.
  • Practice 4: recreate “Expose read-only state with a controlled update” without copying, change one value, and explain the result before running it.
  • Practice 5: recreate “Name a creation policy with a factory” without copying, change one value, and explain the result before running it.

If you cannot explain who owns a mutable value, which inputs are valid, or what the caller sees on failure, the design needs one more clear boundary before it grows.

Common Mistakes

  • Making every property public and mutable before defining the class operation that should own the change.
  • Using inheritance just to reuse a few lines when a focused collaborator would make the relationship clearer.
  • Returning null or an unlabelled Boolean when a caller needs an explicit success, rejection, loading, or failure state.
  • Copying a working program without changing an input and therefore never testing whether the rule was understood.
logo of AlgoGalaxy

Newsletter

Subscribe to our newsletter for weekly updates and promotions.

AlgoGalaxy University

University HomeTechnology LearningCoding ProblemsLearning ProgressCareer OpportunitiesTechnology InsightsUtilNexa ToolsAlgoGalaxy HomeContact Support

AlgoGalaxy CodeRush

CodeRush HomeProblem SetsProgress TrackerLearn TechnologyTechnology JobsEngineering InsightsDeveloper UtilitiesAlgoGalaxy HomePrivacy Policy
Privacy Policy

 • 

Terms of Service

Copyright © AlgoGalaxy 2026