Kotlin Exercises: 100 Practice Problems to Go From Beginner to Pro
Table of Contents
Learning Kotlin syntax is actually pretty easy.
But there is a massive difference between reading Kotlin code…
And actually writing it from scratch.
If you are stuck in “tutorial hell”—watching endless YouTube courses but freezing up the second you open an empty IDE—you are not alone.
Most developers struggle with this exact transition.
Why? Because reading theory doesn’t build muscle memory.
To truly master this language, you don’t need another video tutorial. You need reps. Real, hands-on coding reps.
That is exactly why I put together this master list of Kotlin exercises.
Today, I am giving you 100 hand-picked practice problems designed to take you from absolute beginner to seasoned pro.
I have meticulously organized them into 10 phases of escalating difficulty. You will start with the absolute basics and code your way up to advanced coroutines, state flows, and custom DSLs.
By the time you finish problem #100?
You won’t just “understand” Kotlin. You will have the battle-tested muscle memory and the confidence to build real-world applications from scratch.
Ready to level up your coding skills?
Let’s dive right in.
Phase 1: The Fundamentals (Exercises 1-10)
Welcome to Phase 1.
If you are coming from Java, Python, or Swift, you might be tempted to skip this section.
Don’t.
Kotlin’s syntax is uniquely concise. If you skip the basics, you will end up writing Java code using Kotlin syntax. That is exactly what we want to avoid.
These first 10 problems are designed to break your old habits and introduce you to Kotlin’s control flow, functions, and expressive operators.
Here is your first batch of exercises and exactly what they will teach you:
1. Hello World & Command Line Args
Forget the standard, boring print statement. For this problem, write a program that takes a name as a command-line argument and prints “Hello, [Name]”.
- Why it matters: This forces you to understand Kotlin’s
mainfunction, array arguments, and string interpolation (using the$symbol instead of clunky string concatenation).
2. The Even/Odd Checker
Write a function that accepts an integer and returns whether it is even or odd.
- Why it matters: You are going to use Kotlin’s
whenexpression. It is like aswitchstatement on steroids. You will usewhenconstantly in professional Kotlin development.
3. The Palindrome Checker
Write a program that checks if a string reads the same forwards and backwards.
- Why it matters: String manipulation. You will get a feel for how Kotlin handles strings as character arrays under the hood.
4. Iterative Factorial
Calculate the factorial of a number using a standard for loop.
- Why it matters: Kotlin handles loops differently than most languages. This exercise will introduce you to “Ranges” (e.g.,
for (i in 1..n)), which makes iterating clean and readable.
5. Recursive Factorial
Calculate the factorial of the same number, but this time, use recursion.
- Why it matters: Functional programming is a big part of Kotlin. This teaches you how to write single-expression functions (where the function body is just an
=sign).
6. Idiomatic FizzBuzz
Print numbers from 1 to 100. For multiples of 3, print “Fizz”. For multiples of 5, print “Buzz”. For multiples of both, print “FizzBuzz”.
- Why it matters: Everyone knows FizzBuzz. But doing it idiomatically in Kotlin means combining loops, ranges, and advanced
whenexpressions without using a tangled mess ofif/elseblocks.
7. The Vowel Counter
Take a string of text and count exactly how many vowels are inside of it.
- Why it matters: This is your first introduction to iterating through a string, checking conditions, and maintaining a state counter safely.
8. The Manual Max Finder
Given a hardcoded list of numbers, find the largest number without using Kotlin’s built-in max() function.
- Why it matters: Before you rely on Kotlin’s standard library magic, you need to know how to traverse collections and update variables manually.
9. Temperature Converter
Write two functions: one to convert Celsius to Fahrenheit, and another for the reverse.
- Why it matters: Basic math operations and understanding function return types (like
Int,Double, orFloat). You will learn how Kotlin handles type inference.
10. Build a CLI Calculator
Build a command-line calculator that takes two numbers and a math operator (+, -, *, /) as inputs, and outputs the result.
- Why it matters: This is your first mini-project. It ties together user input, variables,
whenstatements, and basic function architecture into one working program.
Phase 2: Mastering Collections (Exercises 11-20)
Now that you’ve mastered the basics, it’s time to move to the real engine room of the language: Kotlin’s Collections API.
In many languages, processing data—like filtering, transforming, or grouping elements—requires nested loops that are prone to bugs and hard to read. In Kotlin, this is handled through functional, declarative operators. These 10 exercises will move you away from “how” to process data (the loop) and toward “what” you want to achieve (the transformation).
11. Deduplication
Take a list with repeated elements and return a unique list.
- Why it matters: You’ll discover how simple it is to convert between
ListandSetto remove duplicates, or use the.distinct()operator.
12. The Second Largest
Find the second-largest number in an array.
- Why it matters: This teaches you to combine sorting (
sortedDescending()) with indexing, a common pattern for data analysis tasks.
13. Group By
Take a list of strings and group them by their length using groupBy.
- Why it matters: This is your introduction to Maps. You’ll see how a list can be transformed into a structured
Map<Int, List<String>>in a single line.
14. Flattening
Take a list of lists of integers and flatten it into one single list.
- Why it matters: Data often arrives in nested structures (like JSON). Learning
flatten()orflatMap()is crucial for cleaning up complex data objects.
15. Mapping
Transform a list of integers into a list of their squares.
- Why it matters: The
mapoperator is the bread and butter of functional programming. You’ll stop usingforloops to modify lists entirely.
16. Filter Nulls
Given a list that contains both strings and null values, return a list containing only the non-null elements using filterNotNull.
- Why it matters: Kotlin takes null safety seriously. This teaches you how to clean “dirty” data coming from external APIs or user input.
17. Word Frequency
Count how many times each word appears in a paragraph.
- Why it matters: You’ll combine
split,groupBy, andeachCountto perform a real-world task: building a basic search index or frequency counter.
18. Custom Stack
Implement a basic Stack (LIFO: Last-In-First-Out) structure using a MutableList.
- Why it matters: This teaches you how to manage the state of a collection and restrict operations using
addandremoveAt.
19. Zipping
Take two separate lists—for example, names and ages—and combine them into a list of Pair<String, Int>.
- Why it matters: This is essential for merging datasets. You will learn to use
zipto correlate data from different sources perfectly.
20. Partitioning
Use partition to split a list of numbers into two separate lists: one for evens and one for odds.
- Why it matters: Instead of writing two separate filters,
partitiondoes the work in one pass. It’s a perfect example of Kotlin’s focus on performance and readability.
Phase 3: Object-Oriented Kotlin (Exercises 21-30)
You’ve learned to manipulate data; now it’s time to structure it. Kotlin’s approach to Object-Oriented Programming (OOP) is distinct—it removes much of the “boilerplate” (like getters, setters, and constructors) that plagues other languages, allowing you to focus on the architecture.
These 10 exercises will move you from writing simple scripts to designing robust, reusable objects.
21. Data Classes
Create a Person data class and demonstrate how the equals() method automatically handles value-based comparison.
- Why it matters: In Java, you’d write dozens of lines for boilerplate. In Kotlin,
data classdoes it in one. You’ll see exactly why this is the preferred way to hold application state.
22. Bank Account
Implement a BankAccount class using custom accessors to protect the balance.
- Why it matters: This is your first encounter with Encapsulation. You will learn how to expose properties while controlling how they are modified (e.g., preventing negative withdrawals).
23. Abstract Shapes
Create an abstract Shape class with an area() method, and implement it with Circle and Rectangle.
- Why it matters: You’ll learn how to define a contract for classes. This is the foundation of polymorphic behavior in any large-scale application.
24. Singleton Pattern
Implement a DatabaseConnection using the object keyword.
- Why it matters: Kotlin makes the Singleton pattern a first-class language feature. No more complex thread-safe lazy-loading code required.
25. Sealed Classes
Create a sealed class Result with subclasses Success, Error, and Loading.
- Why it matters: This is a “power-user” feature. Sealed classes allow you to represent restricted hierarchies, which is the secret sauce for managing complex UI states in Android or backend logic.
26. Interfaces
Implement a Playable interface in Video and Audio classes.
- Why it matters: Unlike abstract classes, interfaces let you define capabilities that can be mixed into any class, regardless of their position in the inheritance tree.
27. Delegation
Use class delegation (by) to forward interface implementation to another object.
- Why it matters: “Composition over inheritance” is a key design principle. Delegation lets you reuse code without the mess of deep inheritance chains.
28. Enum Classes
Create a CompassDirection enum and add a method that returns the “opposite” direction.
- Why it matters: Enums in Kotlin are more than just lists of constants; they are full-featured classes that can hold state and behavior.
29. Custom Accessors
Write a class where a string property is automatically converted to uppercase when it is set.
- Why it matters: You will learn to use
field(the backing field) to intercept and transform data during property assignment.
30. Factory Pattern
Use a companion object to create a static-like factory method for object instantiation.
- Why it matters: This teaches you how to hide the complexity of object creation, ensuring that users of your class always get a valid, pre-configured instance.
We have covered the basics, collections, and core OOP principles. You are building a solid foundation.
Phase 4: Null Safety & Error Handling (Exercises 31-40)
If you want to know what separates a “junior” developer from a “senior” one, look at how they handle failure.
In many languages, a null value is a ticking time bomb, waiting to throw a NullPointerException (NPE) at the worst possible moment. Kotlin was built specifically to dismantle that bomb.
In this phase, you will stop fearing errors and start managing them gracefully. You’ll learn that in Kotlin, a “null” isn’t a bug—it’s a data state that the compiler forces you to handle safely.
31. Safe Calls
Extract the length of a nullable String? using the safe-call operator (?.).
- Why it matters: You’ll learn how to navigate objects that might not exist without crashing your program.
32. The Elvis Operator
Use the Elvis operator (?:) to assign a default value when a variable is null.
- Why it matters: This is the ultimate “fallback” tool. It allows you to write one-line logic that is both safe and concise.
33. Safe Casting
Attempt to cast an Any type to a String using as?.
- Why it matters: Type safety is hard when you don’t know the exact class of an object. This prevents
ClassCastExceptionby returningnullinstead of crashing.
34. Require
Write a function that validates an input argument using require().
- Why it matters:
require()is your first line of defense for public APIs. It throws anIllegalArgumentExceptionif your function arguments don’t meet your business rules.
35. Check
Use the check() function to verify that your object is in a valid state before running a method.
- Why it matters: Unlike
require(),check()validates the state of your object, not the input arguments. It’s perfect for ensuring a network connection is open before trying to send data.
36. Let Block
Use the let scope function to execute a block of code only if a variable is not null.
- Why it matters: It’s a clean way to perform operations on an object without needing an explicit
if (x != null)check.
37. Custom Exceptions
Create a custom InsufficientFundsException and throw it in your BankAccount logic.
- Why it matters: You’ll learn how to categorize errors so that the rest of your application can react specifically to them.
38. RunCatching
Use runCatching to wrap a risky operation, like parsing an integer from a string, and provide a fallback value.
- Why it matters:
runCatchingis the Kotlin way of doing atry-catchblock, but it returns aResultobject instead of forcing you to handle the exception immediately.
39. Chaining Optionals
Access a deeply nested property (e.g., user?.profile?.address?.zipCode) safely.
- Why it matters: This is the “Aha!” moment for most Kotlin developers. You’ll see how a chain of safe calls can replace 10 lines of nested
ifstatements.
40. Result Type
Write a function that returns a Result<T> instead of throwing an exception.
- Why it matters: You are shifting from “defensive programming” to “functional error handling,” where your code explicitly signals that an operation might fail.
You are officially halfway through the “Safety” core of the language. Now that you know how to keep your code from crashing, are you ready to dive into Phase 5: Functional Programming & Scope Functions, where we turn your code into elegant, idiomatic Kotlin?
Phase 5: Functional Programming & Scope Functions (Exercises 41-50)
If the previous phases were about building the foundation, this phase is about finding your “Kotlin Voice.”
Kotlin is a multi-paradigm language, but its heart is firmly rooted in functional programming. This means treating functions as first-class citizens, using immutable data, and relying on high-level abstractions to do the heavy lifting for you.
The “Scope Functions” (let, run, with, apply, also) are often the most confusing part for beginners. Once you master them, you’ll realize they aren’t just “shortcuts”—they are tools to define the context in which your code executes.
41. Higher-Order Functions
Write a function that takes a list and a custom “predicate” (a lambda) to filter the list.
- Why it matters: You’ll learn how to pass behavior (code) as an argument, just like you pass data. This is the cornerstone of writing flexible, reusable logic.
42. Apply
Use apply to configure a new ArrayList object, adding several items before returning the instance.
- Why it matters:
applyis the king of object initialization. You’ll see how it allows you to group configuration code, making the setup process readable and self-contained.
43. Also
Use also to log the state of an object while it is being initialized.
- Why it matters:
alsois for “side effects.” It lets you peek at or modify an object’s state without changing the flow of the expression. It’s perfect for debugging pipelines.
44. With
Use with to perform multiple operations on a StringBuilder instance without repeating its name.
- Why it matters:
withprovides a “context object” for a block of code, removing the need for repetitive references (e.g.,builder.append(...)).
45. Run
Use run to compute a value based on a locally scoped variable and return it.
- Why it matters:
runis the most versatile scope function. It is a mix ofletandwith—use it when you need to compute a result and you want to operate on an object.
46. Closures
Write a function that returns a “counter” function that increments a private variable.
- Why it matters: You’ll learn how functions can “capture” variables from their outer scope. This is essential for building decorators, stateful callbacks, and event handlers.
47. Fold
Implement your own version of map using the fold function.
- Why it matters:
foldis the “Swiss Army Knife” of functional programming. It allows you to transform a collection into a single value (or a new collection) by accumulating state.
48. Sequences
Use generateSequence to create an infinite Fibonacci series, then take the first 10.
- Why it matters: Lists are “eager” (they calculate everything at once), but Sequences are “lazy” (they calculate as you go). This is crucial for performance when dealing with large datasets.
49. Functional Chaining
Chain map, filter, and reduce to sum the squares of all even numbers in a list.
- Why it matters: This is the ultimate “clean code” test. You’ll see how you can replace 10 lines of imperative
for/iflogic with a single, declarative pipeline.
50. Inline Execution Time
Write an inline higher-order function that measures and prints how long a block of code takes to run.
- Why it matters: You’ll learn why
inlineis a performance optimization that allows Kotlin to skip the overhead of creating function objects, making functional code as fast as raw loops.
You are now writing code that is truly “Kotlin-native.” You’ve moved beyond basic syntax and are now utilizing the language’s power to express complex logic cleanly.
Phase 6: Advanced Language Magic (Exercises 51-60)
By this point, you have the basics down and you’re writing clean, idiomatic code. Now, we’re going to look at the features that make Kotlin feel like you’re writing your own language.
Kotlin is famous for its “syntactic sugar”—features that don’t just make code shorter, but make it significantly more readable and expressive. These exercises will show you how to extend the language to fit your specific needs, rather than just working within the constraints of the standard library.
51. Extension Functions
Write an extension function on String that reverses the characters but maintains the original casing (e.g., “Kotlin” becomes “niltoK”).
- Why it matters: Extensions allow you to add new functionality to classes you don’t even own (like
StringorInt). This is how you build a custom “DSL” feel for your application.
52. Extension Properties
Write an extension property on List<T> called lastIndex that returns the index of the last element.
- Why it matters: Sometimes a property feels more natural than a method. You’ll learn that Kotlin allows you to define custom properties for any class.
53. Infix Functions
Implement an infix function to add two custom Point objects together using a human-readable syntax (e.g., p1 add p2).
- Why it matters:
infixfunctions allow you to drop the parentheses and dots, making your code look like a natural sentence. It’s perfect for building clean APIs.
54. Operator Overloading (+)
Overload the + operator for a custom Vector class so you can add two vectors using the simple + symbol.
- Why it matters: You can make your domain-specific objects behave like native primitives. This turns complex math operations into readable expressions.
55. Operator Overloading ([])
Overload the get operator for a custom Matrix class to allow syntax like matrix[x, y].
- Why it matters: You aren’t just adding symbols; you are enabling standard access patterns for your custom data structures.
56. Tailrec
Write a tailrec function for calculating the Fibonacci sequence.
- Why it matters: Recursion usually risks
StackOverflowError. Thetailrecmodifier tells the compiler to optimize the function into a simple loop, giving you the safety of recursion with the performance of a loop.
57. Type Aliases
Create a typealias for a long, complex function signature (e.g., (Int, String) -> Boolean).
- Why it matters: Your code shouldn’t be hard to read because of complex types. Aliases allow you to replace “alphabet soup” signatures with meaningful, descriptive names.
58. Lazy Delegation
Use by lazy to initialize a heavy object (like a complex configuration) only the first time it is accessed.
- Why it matters: This is a performance superpower. You’ll learn how to defer expensive operations until they are absolutely necessary, reducing application startup time.
59. Observable Delegation
Use Delegates.observable to print a log message every time a property’s value changes.
- Why it matters: This is the easiest way to implement the “Observer” pattern. You’ll see how the language can handle background logic for you, automatically reacting to property changes.
60. Vetoable Delegation
Use Delegates.vetoable to reject updates to a property if the new value is negative.
- Why it matters: You are moving beyond just “storing” data to “enforcing” data integrity at the property level. It turns simple variables into smart, self-validating components.
You’ve successfully mastered the “magic” that makes Kotlin developers so productive. You’re no longer just using the language; you’re shaping the way it interacts with your data.
Phase 7: Generics (Exercises 61-70)
You’ve mastered syntax, collections, and functional programming. Now, we reach the “architectural” level: Generics.
Generics are how you write code that is generic enough to work with any data type, but specific enough to be perfectly type-safe. Without generics, you end up with code that constantly needs messy type-casting, which is a major source of runtime crashes. In Kotlin, generics ensure that if you promise a List<String>, the compiler will stop you from ever putting an Int in there.
61. Generic Box
Create a Box<T> class that can hold any single object type and retrieve it.
- Why it matters: This is your “Hello World” of generics. You’ll understand how to define a type parameter (
<T>) that the class uses internally.
62. Generic Stack
Upgrade your Phase 2 Stack to be a strongly typed Stack<T>.
- Why it matters: Now that you know generics, your stack can work for
Int,String, or even customUserobjects, all while maintaining perfect type safety.
63. Swapper
Write a generic function to swap two elements in a MutableList<T>.
- Why it matters: You’ll learn how to place generic parameters on functions themselves, not just classes. This is essential for writing utility libraries.
64. Type Constraints
Write an average calculator function that only accepts generic types constrained to <T : Number>.
- Why it matters: Sometimes you want to be generic, but not too generic. Constraints allow you to limit types to specific hierarchies (e.g., only numbers can be added).
65. Reified Types
Write an inline function with a reified type parameter to check if a generic list contains an instance of a specific type (e.g., list.hasType<String>()).
- Why it matters: Due to “type erasure” (the JVM forgets generic types at runtime), you usually can’t check types.
reifiedis the “magic” keyword that lets you bypass this limitation.
66. Contravariance (in)
Create an in projected generic interface, such as a Consumer<T>.
- Why it matters: Contravariance allows you to use a
Consumer<Number>where aConsumer<Int>is expected. It’s tricky, but vital for building flexible APIs.
67. Covariance (out)
Create an out projected generic interface, such as a Producer<T>.
- Why it matters: Covariance lets you use a
Producer<Int>where aProducer<Number>is expected. This is the foundation of how Kotlin collections work safely.
68. Star Projections
Use star projection (*) in a function that accepts a list of unknown types and prints its size.
- Why it matters: When you genuinely don’t care what is in a list, star projection is the safe, idiomatic way to express “a list of something.”
69. Generic Extensions
Write an extension function that only applies to List<String>.
- Why it matters: This limits the scope of your extensions, preventing you from polluting the API for lists that contain other types.
70. Mini DI (Dependency Injection)
Build a very basic Dependency Injection container using generics and reified inline functions.
- Why it matters: You’ve reached the peak of this phase. You’ll be building a system that can “find” and “provide” objects based on their type—the exact same mechanism used by industry-standard frameworks like Koin or Dagger/Hilt.
You’ve successfully demystified one of the most complex topics in programming. You’re now ready to move from synchronous logic to the world of concurrency.
Phase 8: Coroutines Basics (Exercises 71-80)
If you want to be a modern Kotlin developer—especially for Android or high-concurrency backend systems—you must understand Coroutines.
In the old days, we managed concurrency with heavy threads, which were expensive and prone to deadlocks. Coroutines are “lightweight threads.” They allow you to write asynchronous code that looks and behaves like synchronous, top-to-bottom code. This phase will move you away from “callback hell” and into the clean, structured world of asynchronous programming.
71. Launch
Launch a basic coroutine using runBlocking and launch.
- Why it matters:
runBlockingacts as a bridge between your main code and the coroutine world, whilelaunchis the primary way to fire off a task that doesn’t return a value.
72. CoroutineScope
Create a class that implements CoroutineScope with its own Job.
- Why it matters: You’ll learn how to define the “lifecycle” of your tasks. If a user leaves a screen or a request is cancelled, you need a way to stop everything related to that specific scope.
73. Suspend Functions
Write a suspend function that simulates a 2-second network call using delay().
- Why it matters: The
suspendkeyword is the “magic” that allows a function to pause its execution without blocking the underlying thread. This is the foundation of non-blocking I/O.
74. Sequential Async
Run two suspend functions one after the other and measure the total time.
- Why it matters: This teaches you the default behavior of coroutines: they execute sequentially unless you explicitly tell them to do otherwise.
75. Concurrent Async
Run those same two functions concurrently using async and await, and measure the time difference.
- Why it matters:
asyncis how you start tasks that return results. You’ll see how easy it is to cut your execution time in half by running tasks in parallel.
76. Dispatchers
Switch threads mid-coroutine using withContext(Dispatchers.IO).
- Why it matters: You cannot do heavy lifting (like database I/O) on the “Main” thread, or your UI will freeze.
Dispatchersare how you shift workloads to optimized thread pools instantly.
77. CoroutineScope Block
Use a coroutineScope {} builder to group tasks, ensuring that if one fails, the others are cancelled.
- Why it matters: This is “Structured Concurrency.” It ensures you don’t have “orphan” background tasks running after an error occurs.
78. Timeout
Handle a long-running task using withTimeout.
- Why it matters: You should never wait forever for a network request.
withTimeoutthrows an exception if the task takes too long, keeping your system responsive.
79. Safe Timeout
Handle a timeout safely using withTimeoutOrNull.
- Why it matters: Sometimes a timeout isn’t an “error”—it’s just a reason to return
null. This is the idiomatic way to handle optional background work.
80. Cancellation
Launch an infinite loop, yield properly, and manually cancel the job after 3 seconds.
- Why it matters: Coroutines must be “cooperative.” You’ll learn how to use
yield()orisActivechecks so that your background tasks respect cancellation requests.
You have just unlocked the ability to build responsive, high-performance applications.
Phase 9: Advanced Coroutines, Flows & Channels (Exercises 81-90)
By now, you can perform one-off asynchronous tasks. But what about real-world data streams? Whether it’s a stream of location updates, incoming chat messages, or a sequence of UI events, you need a way to manage asynchronous data over time.
That is where Flows and Channels come in. If a suspend function is like a single package arriving in the mail, a Flow is like a continuous conveyor belt of packages.
81. Basic Flow
Create a Flow that emits the numbers 1 to 5 with a small delay between each.
- Why it matters: You’ll learn how to “emit” values into a stream and why a flow remains cold (it doesn’t do anything until you start collecting it).
82. Flow Operators
Apply map and filter operators to a Flow before collecting it.
- Why it matters: Just like
List,Flowhas functional operators. You can transform or prune a data stream in real-time as it arrives.
83. Combining Flows
Combine two distinct Flows using zip.
- Why it matters: Real applications often need to merge data from two different sources (e.g., merging a user profile API call with a user settings API call).
84. FlatMap
Flatten a flow of flows using flatMapConcat.
- Why it matters: This is essential when one piece of data triggers another asynchronous stream (e.g., getting a
userIdand then starting a stream ofuserMessagesfor that ID).
85. Flow Exception Handling
Handle exceptions within a Flow stream using the catch operator.
- Why it matters: If one emission in your stream fails, you don’t want the whole app to crash.
catchallows you to recover or provide a fallback value mid-stream.
86. StateFlow
Create a StateFlow to hold and update an application’s state (e.g., a “Loading” vs. “Success” UI state).
- Why it matters:
StateFlowis the industry standard for state management. It always holds the “latest” value, ensuring that any new screen that subscribes to it immediately gets the current state.
87. SharedFlow
Create a SharedFlow to emit one-time events (like showing a Toast notification or navigation) to multiple subscribers.
- Why it matters: Unlike
StateFlow,SharedFlowis perfect for events that shouldn’t be “re-read” if a screen is rotated or recreated.
88. Channels
Implement a producer-consumer pattern using a Channel.
- Why it matters: Channels are communication primitives. Think of them as a thread-safe pipe where one coroutine pushes data and another pulls it.
89. Ticker
Use a flow to emit a “tick” event every 500 milliseconds.
- Why it matters: This teaches you about timing and how to create recurring events without blocking the thread.
90. Buffering
Use the buffer operator on a Flow to handle a slow collector.
- Why it matters: If your data source is fast, but your UI is slow to process it,
bufferlets you store pending items so you don’t lose data or freeze your emitter.
You have reached the final stage of “advanced” mastery. You are now prepared to handle complex, reactive, and event-driven architectures.
Phase 10: Master Level – DSLs, Architecture & Polish (Exercises 91-100)
You have arrived at the finish line. This final phase isn’t about learning new syntax—it’s about learning how to think like a framework author. You will stop just “using” Kotlin and start “bending” it to solve complex architectural problems.
By the end of this phase, you will possess the senior-level skills required to build clean, maintainable, and professional-grade software.
91. HTML DSL
Write a basic Type-Safe Domain Specific Language (DSL) to generate an HTML string using the structure html { body { p { "Hello" } } }.
- Why it matters: You’ll learn how to use
lambda with receiver(Function Literals with Receiver) to create beautiful, readable APIs that look like configuration files.
92. Testing DSL
Write a custom testing DSL that allows for syntax like expect(5) toBe 5.
- Why it matters: Writing great tests is the hallmark of a seasoned dev. Learning to build your own testing matchers makes your test suites readable and easy to debug.
93. Reflection Print
Use Kotlin Reflection to inspect a class at runtime and print all of its property names and types.
- Why it matters: Sometimes you need to know what’s “inside” an object dynamically (like when building serializers or debuggers). Reflection is the “x-ray vision” of the JVM.
94. Custom Annotations
Create a @Todo annotation, apply it to a function, and use reflection to find all functions needing work.
- Why it matters: Annotations are how frameworks (like Room or Dagger) know how to treat your classes. You are learning how to automate code-based workflows.
95. Observer Pattern
Implement the Observer pattern cleanly using Kotlin’s observable delegates or Flows.
- Why it matters: You’ll learn how to decouple your code, ensuring that one class can react to events in another without being tightly coupled.
96. Builder Pattern Override
Demonstrate how Kotlin’s named arguments and default values make traditional Java-style “Builder” classes obsolete.
- Why it matters: A senior developer knows when not to use a design pattern. You’ll learn to simplify your code by leveraging language features instead of verbose design patterns.
97. Kotlin Scripts
Write a .kts script that reads a text file, modifies it, and outputs a new version.
- Why it matters: Kotlin is great for automation! Scripts allow you to write quick, one-off tools that don’t need a massive project build system.
98. Caching Repository
Build a repository class using Generics and Coroutines that fetches data from an interface, caches it, and returns the cached version on subsequent calls.
- Why it matters: This is the bread and butter of modern mobile and web development: managing the “Network vs. Cache” source of truth.
99. Mocking
Write exhaustive unit tests for your Phase 98 repository using a library like MockK.
- Why it matters: Writing the code is only half the job. You must learn to verify it works under all conditions, especially when simulating network failures or API errors.
100. The Capstone Application
Build a complete CLI application: Fetch JSON from a public API, parse it into data classes, process the list concurrently using Flows, handle all nulls safely, and save the result to a local file.
- Why it matters: This is your portfolio piece. It requires everything—generics, coroutines, flows, error handling, and API integration—proving you have transitioned from “learner” to “architect.”
Congratulations! You now have the roadmap to move from a curious beginner to a seasoned Kotlin expert.
Conclusion
There you have it: your ultimate Kotlin Mastery Bootcamp.
You now have a clear, 100-step path to move from “Hello World” to architecting robust, asynchronous, and idiomatic applications.
Here is the secret that most developers miss: You don’t need to be a genius to master Kotlin. You just need to be consistent.
If you spend just 30 to 60 minutes a day tackling one or two of these problems, you will be miles ahead of developers who rely solely on watching passive tutorials. You aren’t just learning syntax; you are building the mental models required to solve real-world problems.
Your Next Move
You have the map. Now you need to start the journey.
Don’t overwhelm yourself by looking at all 100 problems at once. Focus on the one in front of you. Build it, break it, fix it, and understand why it works.
I want to hear from you:
Which phase of this roadmap are you going to tackle first?
Are you going to start with the fundamentals in Phase 1 to ensure your basics are rock-solid, or are you jumping straight into the advanced concurrency of Phase 8?
Drop a comment below and let me know your plan—I’d love to know where your Kotlin journey begins!