Skip to content

The Evolution of Asynchronous Programming: From Rx to Kotlin Coroutines

In frontend and full-stack development, asynchronous programming is an unavoidable topic. Whether it’s JavaScript’s Promise / async-await, or Android/Kotlin’s Future, RxJava, and Coroutines, they are essentially solving the same problem:

How to handle thread scheduling elegantly and make asynchronous logic more controllable.

As senior frontend developers often say:

“Understanding the philosophy of asynchronous mechanisms is more important than mastering a specific framework.”

Different asynchronous frameworks are often just different implementations of the same underlying philosophy.


1. From Future to Rx: Two Asynchronous Philosophies

Initially, in the Java world, the most common asynchronous model was Future. It represents a one-time asynchronous result—when the task finishes, you can retrieve the result, but the mental model remains single-threaded, and the caller must either wait or use a callback to handle the result.

The Future model emphasizes the lifecycle of the task itself, making it a imperative asynchronous logic. It’s close to: “I want to start a task, and I’ll get the result at some point.”

With the advent of Rx (Reactive Extensions), asynchronous programming was redefined as reactive handling of data streams. In Rx, data is a continuous stream, not a one-time result. You no longer care when the task finishes; instead, you subscribe to the stream and react to data changes, combining, filtering, and transforming data in a functional way.

The core idea of this paradigm:

“Describe data changes declaratively, instead of imperatively waiting for results.”

From this perspective, Rx introduces a kind of multithreaded thinking, making asynchronous logic naturally parallel and responsive.


2. Kotlin Coroutines: A “Bridge” Between Future and Rx

With the rise of Kotlin, Google introduced its official asynchronous framework: Kotlin Coroutines. Coroutines are neither pure Future nor fully Rx; they are designed as a bridge between these two philosophies:

  • Inherits Future’s task-blocking model, allowing asynchronous code to be written in a synchronous style.
  • References Rx’s state-flow thinking, using Flow to implement reactive streams.

From a developer’s perspective, Coroutines are very intuitive:

  • Use suspend to write code as if it’s synchronous, while it actually schedules threads non-blockingly.
  • Use Flow to implement reactive data streams without the complexity of Rx operators.

Thus, many describe it as:

“A framework from 1 to infinity”— It handles single asynchronous tasks elegantly while also managing continuous data flows seamlessly.


3. Should You Learn Kotlin Coroutines?

Many developers ask this question.

In fact, you don’t strictly need to learn Coroutines. If you already know RxJava, you can handle almost all asynchronous requirements: data streams, thread switching, error handling, backpressure, etc. Rx is powerful enough.

However, from an ecosystem perspective, Kotlin Coroutines have become the official standard for Android and Kotlin. It integrates deeply with Jetpack, Compose, Room, Retrofit, and other libraries. It’s essentially the default asynchronous language in the Kotlin world.

So, if you want to go deep into modern Kotlin development, learning Coroutines is still worthwhile.


4. The Phenomenon of “Tech Competition” and Mindset

Some developers feel behind if they aren’t learning Coroutines like their peers. This creates a form of “tech competition.”

This technical anxiety is common and not entirely bad. Through this process, you might encounter new paradigms and understand the design philosophy behind frameworks.

But the key is:

Don’t learn frameworks just for the sake of learning frameworks.

The real focus should be on fundamental thinking:

  • asynchronous scheduling models,
  • thread design philosophy,
  • exception propagation logic.

5. Differences in Exception Handling Philosophy

In RxJava, errors propagate via the stream’s onError(). Exceptions don’t actually throw—they propagate as events in the stream. This aligns with Rx’s philosophy: “everything in the data stream is an event.”

In Coroutines, exceptions revert to the traditional try/catch model. It’s more intuitive, but requires a mindset shift. In unit tests, you also need to adjust schedulers and execution environments, e.g., using runTest or TestCoroutineDispatcher.

This difference is more than syntax; it reflects different programming philosophies:

  • Rx emphasizes continuity of streams.
  • Coroutines emphasize structured concurrency.

6. Conclusion: Philosophy Matters More Than Frameworks

Learning asynchronous programming isn’t about chasing the newest framework—it’s about understanding the essence of concurrency and reactive thinking.

Once you truly understand the design logic behind Rx and Coroutines, you’ll realize:

They both solve time-related problems— How to make your code interact with time more naturally, safely, and controllably.

Rx teaches you to think in terms of streams of change.Coroutines teach you to manage asynchronous tasks structurally.

Mastering these two philosophies allows you to write elegant, robust asynchronous code, whether you’re working on frontend, backend, or cross-platform development.

Just something casual. Hope you like it. Built with VitePress