Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
3: Running Kotlin Scripts vs Compiled Programs
In the previous lessons, we looked at the syntax, but now we need to talk about how we actually run the code. In Kotlin, you have two primary paths: writing a script (.kts) or building a compiled program (.kt). I see a lot of developers get these confused, or worse, use scripts for everything because they're "faster" to start, only to hit a wall when they need to distribute their tool to a teammate.
To show you the difference, let's build a simple utility that scans a directory and warns us if any file is larger than 10MB. It's a classic "janitor" task.
Drafting a quick utility with .kts
When I just need to get something done on my own machine, I don't want to deal with project structures or build files. I use a Kotlin Script (.kts). The magic here is that scripts don't require a main function wrapper; the code is just executed from top to bottom.
// file_check.kts
import java.io.File
val threshold = 10 * 1024 * 1024 // 10MB
val dir = File("./logs")
if (!dir.exists()) {
println("Directory not found!")
} else {
dir.listFiles()?.forEach { file ->
if (file.length() > threshold) {
println("Warning: ${file.name} is too large: ${file.length()} bytes")
}
}
}
I can run this immediately using the command line: kotlinc -script file_check.kts. It's great for prototypes. I'm not worrying about classes or packages; I'm just telling the computer to do a task.
The "muscle memory" mistake
Here is where I usually trip up when I'm switching between languages. Out of habit, I started adding a main function to this script because that's what I do in every other Kotlin file. I wrote this:
// Oops, I did this in the .kts file
fun main() {
// ... my logic ...
}
main()
Now, this works, but it's redundant. In a .kts file, the script is already essentially inside a main block. By defining a main function and then calling it manually, I've just added unnecessary boilerplate. I spent five minutes wondering why my script felt clunky before I realized I was treating a script like a compiled program. I stripped the function call out and went back to the top-level code.
Hardening the tool for production
Now, imagine my teammate wants this tool. I can't expect them to have the Kotlin compiler installed just to run a script. I need to turn this into a compiled program. This means renaming the file to FileCheck.kt and wrapping the logic in a proper entry point.
// FileCheck.kt
import java.io.File
fun main(args: Array<String>) {
val path = args.getOrNull(0) ?: "./logs"
val threshold = 10 * 1024 * 1024
val dir = File(path)
if (!dir.exists()) {
println("Directory not found: $path")
return
}
dir.listFiles()?.filter { it.length() > threshold }?.forEach {
println("Warning: ${it.name} is too large")
}
}
Notice I added args here. Scripts are fine for hardcoded paths, but a real program should be flexible. To run this, I compile it to a JVM class file (or a JAR): kotlinc FileCheck.kt -include-runtime -d FileCheck.jar. Now, I can send that .jar file to anyone with a Java Runtime Environment (JRE), and they can run it with java -jar FileCheck.jar /var/log without ever seeing my source code.
Comparing the execution flow
So, when do you choose which? I follow a simple rule of thumb:
- Use .kts (Scripts) for one-off migrations, build scripts, or local automation. If the code is "disposable" or only for you, don't waste time compiling.
- Use .kt (Compiled) for anything that will be used by others, anything that needs to be fast (since it's pre-compiled to bytecode), or anything that grows beyond a single file.
📋 Practical Task
Build a Log-Line Counter Utility
Your task is to create a utility that counts how many lines exist in a specific text file. You will implement this twice to practice the transition from script to program.
- The Script Version: Create a file named
line_count.kts. Write code that reads a file namedapp.log(you can create a dummy text file for this) and prints the total line count. Run it using the-scriptflag. - The Compiled Version: Convert that script into a compiled program named
LineCounter.kt.- Wrap the logic in a
fun main(args: Array<String>). - Modify the program so it takes the filename as a command-line argument (using
args[0]) rather than hardcoding"app.log". - Compile it into a JAR file and run it using the
java -jarcommand.
- Wrap the logic in a
There are no comments for now.