Capstone: A CLI App

Kotlin is a modern, concise language, and in this capstone you'll combine everything you've learned — data classes, enums, objects, collections, and when — to build a complete command-line task manager.

Learn Capstone: A CLI App in our free Kotlin course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

By the end of this lesson you'll have assembled the data model, a singleton manager, and a command-line interface into one working program.

What You'll Build in This Lesson

1️⃣ The Data Model

Start with the data. A Task is a data class using an enum for priority and default arguments . Because it's immutable, we update it with copy() rather than mutation.

That single data class line gave us a readable toString and a copy for immutable updates — concepts straight from earlier lessons.

2️⃣ The Manager (Singleton + Collections)

Next, an object singleton owns the list of tasks and exposes operations built from collection functions like filter , count , and indexOfFirst .

One completed task out of three leaves two pending — and the report is assembled entirely from collection operations. Notice how complete swaps in a copy instead of mutating the task.

3️⃣ The CLI (when Dispatch)

Finally, a command-line layer parses each line and dispatches with a when expression, using safe parsing ( toIntOrNull ) and string helpers. In a real app you'd read lines with readLine() ; here we simulate input.

Every command flows through one when , keeping the logic in one readable place — the same pattern you saw in the control-flow lesson, now driving a real app.

Your turn. Replace the TODO , then run and compare.

Add a feature of your own — high-priority filtering, grouping by priority, or a new command — combining the patterns from the whole course.

📋 Quick Reference — Concepts Used

Practice quiz

What does declaring Task as a data class give you for free?

  • Thread safety
  • A database connection
  • toString, equals, hashCode, and copy
  • Automatic logging

Answer: toString, equals, hashCode, and copy. data classes auto-generate toString, equals, hashCode, and copy.

Why does the capstone update a task with copy(done = true) instead of mutating it?

  • Because Task's val properties make it immutable
  • Because copy is faster
  • Because data classes forbid methods
  • Because the list is read-only

Answer: Because Task's val properties make it immutable. val properties can't be reassigned, so an immutable copy replaces the old task.

What does the object keyword create for TaskManager?

  • A new class per call
  • An abstract class
  • A data class
  • A singleton with one shared instance

Answer: A singleton with one shared instance. object declares a singleton that owns the shared task list.

What does tasks.filter { !it.done } return?

  • The count of done tasks
  • A new list of the not-done tasks
  • The original list mutated
  • A single task

Answer: A new list of the not-done tasks. filter returns a new list keeping elements where the lambda is true.

Why is toIntOrNull() used to parse a command id instead of toInt()?

  • It returns null on bad input instead of throwing
  • It is faster
  • It rounds the number
  • It parses doubles

Answer: It returns null on bad input instead of throwing. toIntOrNull avoids a crash on invalid input by returning null.

What dispatches each command in the CLI's handle function?

  • A long if/else only
  • A for loop
  • A when expression with an else branch
  • A try/catch

Answer: A when expression with an else branch. Commands flow through one when expression, with else for unknown commands.

Why must the command-dispatching when have an else branch?

  • To improve performance
  • As an expression it must be exhaustive and handle unknown commands
  • Because when always needs else
  • To allow break

Answer: As an expression it must be exhaustive and handle unknown commands. A when used as an expression must cover every case, hence else.

What does the Priority enum represent?

  • A mutable list of strings
  • A nullable Int
  • A companion object
  • A closed set of named values: LOW, MEDIUM, HIGH

Answer: A closed set of named values: LOW, MEDIUM, HIGH. An enum class defines a fixed set of named constants.

What does priority: Priority = Priority.MEDIUM in the Task constructor demonstrate?

  • A secondary constructor
  • A default argument value
  • Operator overloading
  • A reflection call

Answer: A default argument value. It is a default parameter value used when the caller omits priority.

What does count { it.done } compute on the task list?

  • A list of done tasks
  • The first done task
  • The number of tasks where done is true
  • The total task count

Answer: The number of tasks where done is true. count with a predicate returns how many elements satisfy it.