RizTech Academy logo
RizTech Academy
Capstone: The Trip SplitterLesson 4 of 535 min

A command-line interface that reads like Kotlin

The model holds the data, the logic computes the settlement — now they need to be wired together into a program somebody can run. This lesson writes the main function that assembles the trip and prints a readable report, and it is where all the pieces connect. The output shown is from the real program.

The entry point

Every runnable Kotlin program starts at fun main() (the getting-started module). Here is the capstone's, in full — read it top to bottom as the story of the program:

package splitter

fun main() {
    val kavita = Person("Kavita")
    val ravi = Person("Ravi")
    val neha = Person("Neha")
    val everyone = listOf(kavita, ravi, neha)

    val expenses = listOf(
        Expense("Hotel", paidBy = kavita, amountPaise = 900000, sharedBetween = everyone),
        Expense("Petrol", paidBy = ravi, amountPaise = 300000, sharedBetween = everyone),
        Expense("Dinner", paidBy = neha, amountPaise = 150000, sharedBetween = everyone),
    )

    printReport(expenses)
}

Notice the details that make it read well:

  • Named arguments — paidBy = kavita, amountPaise = 900000, sharedBetween = everyone. Constructing an Expense reads like a labelled form; nobody can mix up the payer and the amount (the functions module). This is exactly why named arguments mattered.
  • val throughout — nothing here changes, so nothing is a var (the val-by-default habit).
  • A read-only listOf for the expenses and the people (the collections module).
  • main reads as a summary — set up the people, record the expenses, print the report — with the detail one function down. That is the "one thing, one level of abstraction" principle from best-practices.

In a fuller version the expenses would come from user input (readLine()); here they are a fixed trip so the output is reproducible and the lesson stays about structure. Reading input is a small extension you will do in the practice.

Printing a readable report

The report is its own function, so main stays a summary:

fun printReport(expenses: List<Expense>) {
    println("=== Trip to Goa ===")

    println("Expenses:")
    for (e in expenses) {
        println("  ${e.description}: ${formatRupees(e.amountPaise)} paid by ${e.paidBy.name}")
    }

    println("\nNet balances:")
    for ((person, balance) in netBalances(expenses)) {
        val state = if (balance >= 0) "is owed" else "owes"
        println("  ${person.name} $state ${formatRupees(kotlin.math.abs(balance))}")
    }

    println("\nSettlement:")
    val transfers = settle(expenses)
    if (transfers.isEmpty()) {
        println("  Everyone is settled up.")
    } else {
        for (t in transfers) {
            println("  ${t.from.name} pays ${t.to.name} ${formatRupees(t.amountPaise)}")
        }
    }
}

The Kotlin idioms are worth pointing out, because they are the whole course showing up in one function:

  • String templates — "${e.description}: ${formatRupees(e.amountPaise)} paid by ${e.paidBy.name}" reads like the sentence it prints, no + concatenation (the strings module).
  • Destructuring — for ((person, balance) in netBalances(expenses)) unpacks each map entry into a named pair (the collections and data-classes modules).
  • if as an expression — val state = if (balance >= 0) "is owed" else "owes" assigns the result directly, no var (the conditions module).
  • The empty case handled — if (transfers.isEmpty()) prints a friendly message rather than an empty section. Handling the "nothing to do" case is the mark of a finished program, not a demo.

Running it

With the application plugin from the Gradle lesson pointing at splitter.MainKt:

./gradlew run

produces exactly:

=== Trip to Goa ===
Expenses:
  Hotel: ₹9000.00 paid by Kavita
  Petrol: ₹3000.00 paid by Ravi
  Dinner: ₹1500.00 paid by Neha

Net balances:
  Kavita is owed ₹4500.00
  Ravi owes ₹1500.00
  Neha owes ₹3000.00

Settlement:
  Neha pays Kavita ₹3000.00
  Ravi pays Kavita ₹1500.00

There it is — the whole program, from a list of who-paid-what to a clean, human-readable "here is who pays whom". Everything above the Settlement line is context; the two lines under it are the answer the friends actually wanted, and computing them was the point.

What this lesson demonstrates

The main and printReport are almost pure glue — they contain no clever logic, because the logic lives in netBalances and settle where it belongs. That separation is deliberate and is the design:

  • The model (Model.kt) knows the data.
  • The logic (Settlement.kt) knows the algorithm.
  • The CLI (Main.kt) knows how to read input and print output, and nothing else.

Each file does one thing, nameable for it (the project-structure lesson). If you later replaced the command line with an Android screen, you would rewrite only Main.kt — the model and the logic, which are the hard and valuable parts, would not change at all. That is why the settlement was written as plain functions on plain data, with no UI mixed in: it makes the core reusable and testable, which the next lesson relies on.

Check your work

Where a Kotlin program starts. fun main().

Why named arguments in the Expense construction. They label each field, so payer and amount cannot be confused — construction reads like a form.

Why main reads as a summary. The detail lives in printReport, one level down — one thing, one level of abstraction.

The idioms in the report. String templates, destructuring of map entries, if as an expression, and handling the empty case.

Why the empty settlement case is handled. A finished program handles "nothing to do", not just the happy path.

How the program is run. ./gradlew run, with the application plugin pointing at the main class.

The separation the design achieves. Model knows the data, logic knows the algorithm, CLI knows input/output — each file one thing.

Why that separation matters. Replacing the CLI with an Android screen would change only Main.kt; the model and logic (the valuable, testable parts) stay untouched.

Practice

  1. Write main and printReport and run the program with ./gradlew run. Confirm the output matches.
  2. Add a fourth person and a fourth expense, re-run, and confirm the report and settlement update correctly.
  3. Make an expense shared between only some of the people and confirm the balances reflect it.
  4. Create a case where everyone is already even (each person pays their own share) and confirm the report prints "Everyone is settled up."
  5. Replace one hard-coded expense with values read from readLine(), handling the nullable input with ?: or a check.
  6. Extract the "Expenses" printing into its own small function and confirm printReport still reads as a summary.
  7. Change the CLI output format (for example, group the settlement by who-pays) without touching Settlement.kt. Note that the logic did not need to change.

Official documentation

Next: testing it, and the bugs the tests find.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship