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
- Control flow: if/else as an expression, loops
- Ranges & progressions with in and when
- Strings: templates and everyday methods
- Arrays and List, type checks with is
- Visibility modifiers and encapsulation
- Destructuring, lateinit, and by lazy
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.
| Topic | One-line reminder |
|---|---|
| Control flow | if is an expression; for loops over ranges/collections |
| Ranges | 1..5, until, downTo, step, in |
| Strings | Templates $x/$${e}, methods, multiline |
| Arrays | Fixed-size, indexed; mutable elements, vs List |
| Type checks | is + smart cast; as? safe cast |
| Visibility | public/private/protected/internal |
| Destructuring | val (a, b) = pair; maps, lambdas, _ |
| lateinit & lazy | lateinit 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
- ❌ Integer division where you wanted a fraction. ✅ Convert first: total.toDouble() / size.
- ❌ Mixing up .. (inclusive) and until (exclusive). ✅ Re-check which endpoint you need.
- ❌ Destructuring in the wrong order. ✅ It's positional — align with the class's properties.
- ❌ Reading a lateinit before assigning it. ✅ Set it first or guard with ::p.isInitialized.
Pro Tips
- 💡 Extract helpers: pulling grading logic into gradeFor() keeps main readable.
- 💡 Lean on when + in for any "bucket a number into a category" task.
- 💡 Destructure map and pair loops instead of indexing — it reads far better.
🎉 Checkpoint Complete!
- ✅ You combined control flow, ranges, strings, and destructuring in one program
- ✅ You used a range inside when to classify values
- ✅ You handled Double division for an accurate average
- ✅ You reviewed type checks, visibility, and lateinit/lazy via the quiz
- ✅ The basics are solid — time to level up
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
- Previous: lateinit & lazy
- Next: Higher-Order Functions & Function Types — Pass and return functions, function references, and trailing lambdas
- Quick reference: Kotlin cheat sheet
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.