
The Basics: Main Routine and Subroutines
In traditional programming, execution flow is structured around a main routine (the entry point of the program) and subroutines (helper functions that perform specific tasks).
What is a Main Routine?
The main routine (often called main()
) is:
- The entry point of every program.
- The first function that executes when you run your code.
- Responsible for orchestrating the program’s workflow.
- In Kotlin/Java/C-style languages, it looks like this:
fun main() { // Program starts executing here first() second() }
Key characteristics:
- Mandatory — Every executable program must have one
- Parent function — Calls other subroutines
- Lifecycle controller — Determines the order of operations
What are Subroutines?
Subroutines (also called functions/methods) are:
- Reusable blocks of code that perform specific tasks
- Called by the main routine or other subroutines
- Modularize code into logical units
fun first() { // Does task A } fun second() { // Does task B }
Key characteristics:
- Single Responsibility — Each should do one thing well
- Callable — Can be invoked multiple times
- Stack-based — Follows Last-In-First-Out (LIFO) execution
fun main() { first() //subroutine second() //subroutine } fun first() { var first = 1 while (true) { println("first: ${first++}") } } fun second() { var second = 1 while (true) { println("second: ${second++}") } }
What actually happens:
- Program launches →
main()
starts executing main()
callsfirst()
→ execution jumps tofirst()
first()
enters infinite loop → never returnssecond()
never gets called because control never returns tomain()
The Critical Limitation
This demonstrates a fundamental programming constraint:
- Subroutines are blocking — The caller waits until completion
- No concurrency — Only one function executes at any moment
- Order-dependent — Strict sequential execution
Visualizing the Call Stack
Call Stack State ----------- -------- Program starts main() Calls first() first() Enters loop [stack frozen] first() never returns
This blocking behavior is exactly what coroutines solve by introducing:
- Suspendable functions
- Non-blocking operations
- Concurrent execution

In our previous example with traditional subroutines, we saw how first()
‘s infinite loop completely blocked execution of second()
. Now let’s examine how coroutines revolutionize this behavior:
fun main(): Unit = runBlocking { launch { first() } // coroutine 1 launch { second() } // coroutine 2 } suspend fun first() { var first = 1 while (true) { println("first: ${first++}") delay(2000) // Suspension point } } suspend fun second() { var second = 1 while (true) { println("second: ${second++}") delay(1000) // Suspension point } }
Key Differences from Subroutines
1. Non-Blocking Concurrent Execution
- Both functions now run concurrently instead of sequentially
- Output will interleave: “second” prints twice as often as “first”
- Neither function blocks the other
2. The Magic of Suspension Points
delay()
is a suspension function that pauses execution without blocking- When encountered, the coroutine:
- Yields the thread
- Schedules resumption
- Allows other coroutines to run
3. Structured Concurrency
runBlocking
creates a coroutine scopelaunch
starts new coroutines as children of this scope- All coroutines are automatically cancelled when the scope completes
How This Works Internally
Coroutine Execution Timeline
Time (ms) | Coroutine 1 (first) | Coroutine 2 (second) ----------|--------------------------|---------------------- 0 | Prints "first: 1" | | Suspends for 2000ms | Prints "second: 1" | | Suspends for 1000ms 1000 | | Resumes, prints "second: 2" | | Suspends for 1000ms 2000 | Resumes, prints "first: 2"| | Suspends for 2000ms | Prints "second: 3" 3000 | | Resumes, prints "second: 4" ... | ... | ...
Why This Matters
- Resource Efficiency
- Traditional threads: ~1MB stack per thread
- Coroutines: ~50 bytes per suspended coroutine
2. Simplified Concurrency
- No callback hell
- Sequential-looking code with asynchronous execution
3. Responsive Applications
- Never blocks the main thread
- Easy to implement features like:
- Parallel network requests
- Animations with delays
- Background processing
Visual Comparison
Subroutine Execution
main() └── first() (runs forever) └── second() (never reached)
Coroutine Execution
runBlocking ├── launch { first() } (suspends/resumes) └── launch { second() } (suspends/resumes)

Job Offers
Conclusion
This demonstrates the paradigm shift from linear, blocking execution to flexible, non-blocking concurrency. By leveraging suspension points and structured concurrency, coroutines enable efficient multitasking without sacrificing code clarity. This evolution from rigid subroutine execution to flexible coroutines represents a fundamental advancement in asynchronous programming.
Hope you liked this article. Happy Coding!!
This article is previously published on proandroiddev.com.