Checkpoint: Kotlin Basics

Reviewed & published by Brayan K

This checkpoint is a hands-on review that ties together everything from the basics track — control flow, ranges, strings, arrays, type checks, visibility, destructuring, and lateinit/lazy — into one build challenge and a short quiz.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Work through it to confirm the foundations are solid before stepping up to higher-order functions.

What This Checkpoint Covers

1️⃣ Recap at a Glance

Before the challenge, here's a compact map of the eight skills you've built. Each links back to its full lesson if you want a refresher.

TopicOne-line reminder
Control flowif is an expression; for loops over ranges/collections
Ranges1..5, until, downTo, step, in
StringsTemplates $x/$${e}, methods, multiline
ArraysFixed-size, indexed; mutable elements, vs List
Type checksis + smart cast; as? safe cast
Visibilitypublic/private/protected/internal
Destructuringval (a, b) = pair; maps, lambdas, _
lateinit & lazylateinit var set later; by lazy once

2️⃣ Warm-Up

A short mixed example to shake off the rust — it touches control flow, ranges, strings, and destructuring all at once. Trace it before you run it.

fun main() {
    // A quick mixed warm-up touching several skills:

    // Control flow as an expression:
    val n = 6
    val parity = if (n % 2 == 0) "even" else "odd"
    println("$n is $parity")          // -> 6 is even

    // A range + step, joined cleanly:
    val evens = (0..6 step 2).toList()
    println(evens.joinToString(" "))   // -> 0 2 4 6

    // String template + method:
    println("kotlin".uppercase())      // -> KOTLIN

    // Destructuring a Pair:
    val (a, b) = Pair(10, 20)
    println(a + b)                     // -> 30
}

3️⃣ Build Challenge: Student Report

Now combine the skills. Process a list of students with destructuring, classify each score with a range inside when, format output with string templates, and finish with a computed average. The starter below has the data and the spec — write the body, then reveal the solution to check yourself.

One clean way to write it, using a small helper function for the grading logic:

📝 Checkpoint Quiz

Answer each before expanding it. No peeking!

That if in Kotlin is an expression: it returns a value, so it replaces the ternary operator other languages have. The else is required when used this way.

1 until 5 visits 1, 2, 3, 4 (excludes the top), while 1..5 visits 1, 2, 3, 4, 5 (includes both ends). until is the half-open range.

Arrays use the default object representation, so you see something like [Ljava.lang.Integer;@.... Use .toList() or .joinToString() to print the elements.

Because of a smart cast: once the is check passes, the compiler treats x as a String inside that block, so no explicit as is needed.

By position. The first variable receives component1() (the first property) no matter what you name it, so the order must match the class's properties.

lateinit var is a non-null var you assign manually before use. by lazy is a val computed automatically on first access and cached. Use lateinit for injected values, lazy for deferred computation.

Watch Out For

Pro Tips

🎉 Checkpoint Complete!

Practice quiz

What does val max = if (a > b) a else b demonstrate about Kotlin's if?

  • if is only a statement
  • if is an expression that returns a value
  • if requires curly braces
  • if cannot have an else

Answer: if is an expression that returns a value. In Kotlin if is an expression that returns a value, so it replaces the ternary operator and the else is required.

Which numbers does for (i in 1 until 5) iterate over?

  • 1, 2, 3, 4, 5
  • 1, 2, 3, 4
  • 0, 1, 2, 3, 4
  • 2, 3, 4, 5

Answer: 1, 2, 3, 4. until is a half-open range, so 1 until 5 visits 1, 2, 3, 4 and excludes the top endpoint.

How do you check whether a value is inside the range 1..10?

  • x within 1..10
  • 1..10 has x
  • x in 1..10
  • x between 1, 10

Answer: x in 1..10. The in operator tests membership: x in 1..10 is true when x is between 1 and 10 inclusive.

Why does println(arrayOf(1, 2, 3)) print a cryptic value?

  • Arrays cannot be printed
  • It uses the default object representation, not the elements
  • The array is empty
  • Integers are not printable

Answer: It uses the default object representation, not the elements. Arrays use the default Object toString, so you must use joinToString() or toList() to print the elements.

After if (x is String), why can you call x.length without a cast?

  • String has no length
  • Kotlin smart-casts x to String inside the block
  • You still need an explicit as cast
  • is always returns a String

Answer: Kotlin smart-casts x to String inside the block. Once the is check passes, the compiler smart-casts x to String within that scope, so no explicit cast is needed.

In val (name, age) = user, how are the variables matched?

  • By name
  • By type
  • By position
  • Alphabetically

Answer: By position. Destructuring is positional: the first variable gets component1(), the second component2(), regardless of name.

Which visibility modifier limits a declaration to the same module?

  • private
  • protected
  • internal
  • public

Answer: internal. internal restricts visibility to the same module; private is file/class scope, protected adds subclasses, public is everywhere.

What is the key difference between lateinit var and by lazy?

  • They are the same
  • lateinit is a var you assign manually; by lazy is a val computed on first access
  • lazy must be a var
  • lateinit caches its value automatically

Answer: lateinit is a var you assign manually; by lazy is a val computed on first access. lateinit var is assigned manually before use, while by lazy is a val computed automatically on first read and cached.

How do you get a fractional average from two Ints, total and size?

  • total / size
  • total.toDouble() / size
  • total % size
  • Double(total / size)

Answer: total.toDouble() / size. Int / Int does integer division; convert first with total.toDouble() / size to force floating-point division.

Which expression produces the list [0, 2, 4, 6]?

  • (0..6 step 2).toList()
  • (0..6).toList()
  • (0 until 6).toList()
  • (0..6 step 3).toList()

Answer: (0..6 step 2).toList(). A range with step 2 from 0..6 yields 0, 2, 4, 6, and toList() materializes it.

Continue this course

Frequently asked questions

What should I be able to do after this checkpoint?

You should be comfortable branching with if as both a statement and an expression, looping over ranges and collections, building and testing ranges with in, manipulating strings via templates and methods, indexing arrays, narrowing types with is and smart casts, controlling access with visibility modifiers, unpacking values with destructuring, and deferring initialization with lateinit and lazy. The build challenge here weaves several of these together exactly the way real Kotlin code does.

Why does the average print as 80.33333333333333 instead of 80.33?

Because total.toDouble() / size produces a full-precision Double, and Kotlin's default println shows every significant digit. To round for display you would format it, for example with "%.2f".format(average), which would yield 80.33. The checkpoint prints the raw Double on purpose so the output is fully deterministic and easy to verify.

Do I need the data class and helper function the challenge mentions?

Not strictly — the solution shown uses a small gradeFor helper function and skips a data class, which is perfectly idiomatic. The suggestions are there to encourage good structure: pulling the grading logic into its own function keeps main readable and makes the when expression reusable. As you tackle later lessons, organizing logic into focused functions and classes becomes second nature.

I'm not confident on one of the recap topics — what should I do?

Revisit that specific lesson before moving on. Each topic in this checkpoint links back to a dedicated lesson with worked examples you can run in the Kotlin Playground. The next lesson, Higher-Order Functions, builds directly on a solid grasp of these basics, so it pays to fill any gaps now rather than later.

Related lessons