The problem async/await does not solve by itself
Asynchrony does not remove complexity; it changes where complexity lives. Without an explicit structure, tasks may outlive the context that created them, failures become detached, and cancellation turns from a guarantee into a convention.
The contract of a CoroutineScope
In Kotlin, a scope defines lifetime and responsibility. Structured concurrency guarantees that a block only completes when its children do. The job hierarchy becomes a representation of the program flow itself—not a detail hidden inside callbacks.
Fail together or supervise
coroutineScope propagates a child failure and cancels its siblings; supervisorScope isolates failures when each task can produce value independently. This is a semantic choice: it should express a domain rule, not merely suppress an exception.
A practical heuristic
If the caller needs the result to proceed, keep the task inside its scope. If an operation must outlive the request, give it an explicit owner—a queue, worker, or service with its own lifecycle. GlobalScope is almost never that owner.
If this idea helped you, share it with someone else who builds systems.