Corrutinas de Kotlin: de la máquina a producción
Corrutinas de Kotlin como tema propio: sin Spring, sin Reactor y sin frameworks en el medio. 118 lecciones en 22 partes, desde en qué compila realmente suspend hasta operar corrutinas en producción. Cada práctica corre contra la librería REAL kotlinx.coroutines — y contra kotlinx-coroutines-test, así que el reloj virtual de runTest hace exactos los tiempos: «50.000 corrutinas terminaron en 10 ms sobre un hilo» es una aserción, no una promesa.
Aprendamos juntos.
Pregúntame sobre este curso o por dónde empezar.
En qué compila realmente `suspend`, por qué una corrutina suspendida no retiene ningún hilo, y qué te da cada uno de los tres builders.
- Paso 1
What `suspend` really is
Every asynchronous story starts with the same problem: a thread that waits is a thread you cannot use. A JVM thread cost…
Empezar lección - Paso 2
Continuations: what the compiler actually generates
Coroutines feel like magic until you see the transform, and then they stop being magic forever. The compiler rewrites ev…
Empezar lección - Paso 3
The cost model: why 50,000 coroutines is unremarkable
Concurrency arguments usually stall on vibes, so put numbers on it. A JVM thread reserves roughly 1 MB of stack by defau…
Empezar lección - Paso 4
launch, async, runBlocking — and withContext
You cannot call a suspend function from ordinary code — lesson 1's rule — so something has to create the first coroutine…
Empezar lección - Paso 5
Capstone: sequential by default, concurrent on purpose
Part I ends where most real slowdowns begin. A handler calls three services, each takes 100 ms, and the endpoint takes 3…
Empezar lección
Scopes, el árbol de Jobs y sus estados, la cancelación como protocolo cooperativo, los modos de arranque, y la limpieza que nunca corre en silencio.
- Paso 6
Scopes and the Job tree
A `CoroutineScope` sounds grand and is almost nothing: an interface with a single property, `coroutineContext`. What giv…
Empezar lección - Paso 7
The Job lifecycle
Every builder hands you a `Job`. `launch` returns one directly, `async` returns a `Deferred<T>` which *is* a `Job` with …
Empezar lección - Paso 8
coroutineScope vs supervisorScope
You have two scope functions and they look interchangeable. They are not. `coroutineScope { }` and `supervisorScope { }`…
Empezar lección - Paso 9
Cancellation is cooperative
Cancellation is what makes structured concurrency worth having: a disconnected client, a fired `withTimeout`, a destroye…
Empezar lección - Paso 10
Cleanup that survives cancellation
This is the cancellation bug that reaches production, and it is not a typo — it is the contract from lesson 8 applied co…
Empezar lección - Paso 11
Start modes: DEFAULT, LAZY, ATOMIC, UNDISPATCHED
`launch` and `async` both take a `start:` parameter, and almost nobody passes it. That is usually right — `CoroutineStar…
Empezar lección - Paso 12
Capstone: a job you can actually stop
Part II has given you four separate facts: a scope owns a Job tree, the failure policy is a choice, cancellation is coop…
Empezar lección
Correr cosas a la vez a propósito: async/await, deadlines, carreras con select, acotar el fan-out, y dónde aparece realmente una falla.
- Paso 13
async and Deferred, in depth
`async` returns a `Deferred<T>`, which is a `Job` with a result attached. Everything you know about Jobs applies: it is …
Empezar lección - Paso 14
Timeouts and deadlines
A call with no deadline is a call that can hang forever, and in a system with a connection pool that is how one slow dep…
Empezar lección - Paso 15
select — waiting on several things at once
Everything so far waits for one *named* thing: `await()` this Deferred, `receive()` from this channel, `join()` this Job…
Empezar lección - Paso 16
Bounded fan-out
Part I sold you on cheap concurrency, and it is true: launching coroutines costs almost nothing. But the coroutines are …
Empezar lección - Paso 17
Where an exception actually goes
Every confusing thing about coroutine exceptions comes from one fact: **`try/catch` unwinds the call stack, and a corout…
Empezar lección - Paso 18
Capstone: a call that is bounded, timed and retried
Part III has given you the pieces separately: `async` for concurrency, `withTimeoutOrNull` for a deadline, `select` for …
Empezar lección
El CoroutineContext como mapa tipado, qué dispatcher elegir y por qué, limitedParallelism, y cómo se muere de hambre un pool de hilos.
- Paso 19
CoroutineContext: the typed set every coroutine carries
A `CoroutineContext` is an **immutable, indexed set of elements**, where the index is the element's type. It behaves lik…
Empezar lección - Paso 20
Dispatchers in production
A dispatcher decides which thread runs a coroutine when it resumes. There are four you will meet, and picking between th…
Empezar lección - Paso 21
limitedParallelism and custom dispatchers
`Dispatchers.Default` and `Dispatchers.IO` are not two thread pools. They are two **views of the same pool**, with diffe…
Empezar lección - Paso 22
CPU-bound, blocking, and the starvation in between
Every dispatcher choice reduces to one question. **Does this code occupy a thread while making no progress?** If it does…
Empezar lección - Paso 23
CoroutineContext, names and correlation ids
A `CoroutineContext` is an immutable set of elements indexed by type: a `Job`, a dispatcher (`ContinuationInterceptor`),…
Empezar lección
Estado mutable compartido y las cuatro formas de sobrevivirlo: confinamiento, atómicos, Mutex y Semaphore — y los deadlocks que cada una invita.
- Paso 24
Shared mutable state
The most dangerous misconception about coroutines is that a single-threaded dispatcher makes shared state safe. It does …
Empezar lección - Paso 25
The four races, and how to make one reproduce
The previous lesson showed *that* shared mutable state breaks. This one names the four shapes it breaks in, because each…
Empezar lección - Paso 26
Mutex: a lock that suspends instead of blocking
`kotlinx.coroutines.sync.Mutex` looks like `synchronized` and is a different animal. When it is contended it **suspends*…
Empezar lección - Paso 27
Semaphore: a quota that travels with the coroutine
A `Semaphore(permits)` is a counter with a queue. `acquire()` takes a permit or suspends until one is free; `release()` …
Empezar lección - Paso 28
Deadlock, livelock, starvation
All three of these look identical from the outside — the work stops — and they need completely different fixes. One ques…
Empezar lección
La cola entre corrutinas: capacidad, política de desborde, worker pools, pipelines, actores y estado con un único dueño.
- Paso 29
Channels: the queue between coroutines
A `Channel<T>` is the coroutine equivalent of a `BlockingQueue`: one coroutine puts elements in, another takes them out.…
Empezar lección - Paso 30
Capacity and overflow policy
A channel's capacity is not a tuning knob, it is a semantic choice. `Channel.RENDEZVOUS` (capacity 0, the default) means…
Empezar lección - Paso 31
Fan-out and fan-in
Once you accept that every element on a channel has exactly one owner, the worker pool writes itself. **Fan-out** is sev…
Empezar lección - Paso 32
Pipelines and produce
A pipeline is a chain of coroutines where each stage consumes one channel and produces another. Stage 1 reads rows, stag…
Empezar lección - Paso 33
Actors: state with an inbox
An actor is a coroutine that owns some state and receives messages through a `Channel`. Nothing outside can reach the st…
Empezar lección - Paso 34
Capstone: confinement — state with exactly one owner
Part IV ends on the pattern that channels make possible and that most teams underuse. Instead of protecting shared state…
Empezar lección
Streams asíncronos fríos: el set de operadores, operadores terminales, flowOn y preservación de contexto, contrapresión, y escribir tu propio operador.
- Paso 35
Flow: the cold asynchronous stream
A `suspend fun` returns one value. A `Channel` is a hot queue with one owner per element. `Flow<T>` is the third shape: …
Empezar lección - Paso 36
Operators: transforming a stream
Operators are the vocabulary, and they divide cleanly into two kinds. **Intermediate** operators — `map`, `filter`, `tra…
Empezar lección - Paso 37
Terminal operators: the only things that make a flow run
A `Flow` built from `flow { }`, `map`, `filter` and `take` has done nothing at all. Those are **intermediate** operators…
Empezar lección - Paso 38
flowOn and the context preservation rule
Flow has one structural rule and it is enforced at runtime: **`emit` must happen in the same coroutine context the colle…
Empezar lección - Paso 39
Sequential by default: buffer, conflate, collectLatest
The most important property of a `Flow` is the least advertised: it is **sequential**. `emit(x)` is a suspending call th…
Empezar lección - Paso 40
The Flow contract and declarative error handling
The `flow { }` builder enforces two invariants, and both failures are famous enough to be worth meeting deliberately rat…
Empezar lección - Paso 41
Capstone: write your own operator
There is no operator registry in Kotlin. `map`, `filter`, `debounce` and every other operator is an ordinary extension f…
Empezar lección
StateFlow vs SharedFlow, ciclos de vida de shareIn, combinación de streams, debounce y sample — y cómo elegir entre Channel, Flow y StateFlow.
- Paso 42
StateFlow and SharedFlow
Everything in Part V was **cold**: a recipe, re-run per collector. Hot flows are the other half. They run **once**, inde…
Empezar lección - Paso 43
shareIn and stateIn: cold to hot
A cold `Flow` runs once per collector: two collectors mean two database queries, two websocket connections, two subscrip…
Empezar lección - Paso 44
Channel, Flow, SharedFlow, StateFlow: choosing
By this point in the course you have four ways to get a value from one coroutine to another, and the hardest part of usi…
Empezar lección - Paso 45
Combining flows: combine, zip, flatMapLatest
Once you have more than one stream you need to join them, and the three operators that do it are not interchangeable. Pi…
Empezar lección - Paso 46
debounce, sample and throttle
Some streams arrive faster than anything downstream should react to: keystrokes, mouse moves, sensor readings, a websock…
Empezar lección - Paso 47
Capstone: state, events and one shared upstream
Part VI has given you the three pieces separately. This capstone puts them in the arrangement they almost always appear …
Empezar lección
runTest y el reloj virtual — la parte que convierte la temporización de fuente de inestabilidad en una aserción confiable.
- Paso 48
runTest and the virtual clock
Every practice in this course has run inside `runTest`, and every timing claim has been exact. That is not a coincidence…
Empezar lección - Paso 49
Testing flows
Testing a **cold** flow is almost too easy, and that is the point of coldness. `toList()` inside `runTest` drives it to …
Empezar lección - Paso 50
Injecting dispatchers
Lesson 31 ended on the constraint that governs this whole part: **virtual time only applies to the test dispatcher.** Wo…
Empezar lección - Paso 51
Testing cancellation, timeouts and failures
Happy paths mostly test themselves. The paths that actually break in production — a cancelled request, an expired deadli…
Empezar lección - Paso 52
Testing channels and hot flows
Testing a cold flow is pleasant: `flow.toList()` gives you everything and you assert on a list. Hot streams take that aw…
Empezar lección - Paso 53
Capstone: pin a timing-sensitive component
Part III built a resilient call: bounded, timed and retried. It is exactly the kind of code that rots, because everyone …
Empezar lección
Nombrar corrutinas para que un stack trace signifique algo, encontrar la que se colgó, y medir lo que realmente cuesta suspender.
- Paso 54
Making a coroutine identifiable
The first problem with debugging coroutines is that they have no identity. A thread has a name and shows up in every dum…
Empezar lección - Paso 55
Finding the coroutine that stopped
This is the single biggest operational difference between threads and coroutines. A hung thread is *visible*: it is in e…
Empezar lección - Paso 56
What a coroutine actually costs
"Coroutines are cheap" is true and useless. Cheap compared to what, and which part? There are three distinct costs, they…
Empezar lección - Paso 57
Paying for concurrency you are not using
The costs from the previous lesson are only interesting where they are *wasted*. Two patterns waste them systematically,…
Empezar lección - Paso 58
Measuring coroutine code honestly
There are two clocks in this course and they measure different things. **The virtual clock tells you when things happen;…
Empezar lección
La otra mitad de todo código real: JDBC, archivos, callbacks y futures — envueltos para que no bloqueen el pool equivocado ni se filtren al cancelar.
- Paso 59
Spotting blocking code
Coroutines are a story about not holding threads, and most of the JVM ecosystem predates it. JDBC blocks. `java.io` bloc…
Empezar lección - Paso 60
Wrapping blocking code correctly
You have found the blocking call and you cannot replace it. The correct wrapper is short, and every piece of it earns it…
Empezar lección - Paso 61
Turning a callback into a suspend function
Every callback API has the same shape: you hand it a function, it calls that function later, on a thread of its choosing…
Empezar lección - Paso 62
Futures in, futures out, and streams of callbacks
Most of the bridges you need already exist. `kotlinx-coroutines-jdk8` supplies `CompletableFuture.await()` for a future …
Empezar lección - Paso 63
Transactions, connections and coroutine affinity
A database transaction lives on **one connection**. `BEGIN`, every statement, and `COMMIT` must all travel down the same…
Empezar lección
Reactive Streams desde los primeros principios — demanda y contrapresión — y luego Mono/Flux a suspend/Flow y de vuelta, sin mezclar los dos estilos.
- Paso 64
Reactive Streams: four methods and one number
Reactive Streams is a specification, not a library — four interfaces that RxJava, Reactor, Akka Streams and the JDK's `F…
Empezar lección - Paso 65
Mono and Flux, coming in
Reactor types arriving at your code — from Spring's `WebClient`, from R2DBC, from the reactive Mongo driver — become sus…
Empezar lección - Paso 66
Suspend functions, going out
The other direction is the one that lets you adopt coroutines inside an application whose framework, callers, or half of…
Empezar lección - Paso 67
Back-pressure across the boundary
Both worlds solve the same problem — stopping a fast producer from burying a slow consumer — and they express it differe…
Empezar lección - Paso 68
One style per layer
Because the conversions are one-liners, it is easy to sprinkle them through a codebase until every layer speaks both lan…
Empezar lección
Controllers suspend, servicios y repositorios con corrutinas, cancelación por request, y llamadas paralelas aguas abajo que respetan un deadline.
- Paso 69
Suspending controllers
Spring WebFlux has understood coroutines natively since 5.2. A controller method may be a **`suspend fun`** returning an…
Empezar lección - Paso 70
Layering: where the framework stops
The layering question is the one from Part XII's last lesson, made concrete. **Which layer is allowed to mention `Mono`,…
Empezar lección - Paso 71
When the client hangs up
A user closes the tab. A load balancer times out. A mobile client loses signal. In a thread-per-request server this prod…
Empezar lección - Paso 72
Parallel downstream calls with a deadline
An aggregating endpoint is the most common shape in a service-oriented backend: gather from several dependencies, assemb…
Empezar lección - Paso 73
Scopes, dispatchers and shutdown as configuration
Most coroutine code in a Spring application needs no configuration at all — a suspending controller calling a suspending…
Empezar lección
Persistencia reactiva y brokers vistos con corrutinas: límites del pool de conexiones, fronteras transaccionales, concurrencia de consumidores e idempotencia.
- Paso 74
Streaming a result set instead of holding it
Reactive drivers — R2DBC, the reactive Mongo driver, a paging HTTP API — all expose the same shape underneath: a **curso…
Empezar lección - Paso 75
Batching writes, and where to stop
Writing rows one at a time pays the round trip every time. Two hundred inserts at 10 ms of latency each is **2,000 ms**,…
Empezar lección - Paso 76
Consuming a broker: the poll loop as a Flow
Every message broker's consumer is the same loop: poll for a batch, process it, record how far you got. Kafka calls the …
Empezar lección - Paso 77
Ordering and consumer concurrency
A message consumer has two demands that pull against each other: **process faster** and **process in order**. Handling o…
Empezar lección - Paso 78
Idempotency, retries and dead letters
The previous lesson left you with a guarantee that duplicates are certain. This one makes that harmless. **An idempotent…
Empezar lección
Reintentos con backoff y jitter, circuit breakers, bulkheads, fallbacks y propagación de deadlines — escritos como funciones suspend, no como anotaciones.
- Paso 79
Retry, back-off and jitter
Retrying is the cheapest resilience there is and the easiest to get catastrophically wrong. A retry sends *more* traffic…
Empezar lección - Paso 80
The circuit breaker
Retrying helps a dependency that is briefly unwell. It actively harms one that is *down*: every call waits for a timeout…
Empezar lección - Paso 81
Bulkheads: one dependency's bad day
A ship's bulkhead does not stop a hull breach; it stops the breach from flooding the whole ship. The pattern is the same…
Empezar lección - Paso 82
Fallbacks and graceful degradation
Failing fast is only half a strategy. The other half is deciding what the user sees when a dependency is unavailable — a…
Empezar lección - Paso 83
Deadlines that shrink
Part XIII established the rule: one budget at the top, carried down by cancellation. This lesson is the version for a ca…
Empezar lección
Diseñar para carga: worker pools, batching y chunking, pipelines paralelos, y elegir dónde se permite que se acumule la contrapresión.
- Paso 84
Worker pools and draining
The worker pool is the oldest concurrency shape there is and the simplest one to build with coroutines: a `Channel` for …
Empezar lección - Paso 85
Long jobs, checkpoints and resume
A batch that takes six hours will be interrupted: a deploy, a pod eviction, an OOM, a bad row at 80%. Without a checkpoi…
Empezar lección - Paso 86
Pipelines and the bottleneck
A multi-stage pipeline — fetch, parse, write — built from channels and worker pools is one of the most useful shapes in …
Empezar lección - Paso 87
Where the queue lives
When arrival rate exceeds service rate, a queue forms. That is arithmetic, not a design flaw — the only design decision …
Empezar lección - Paso 88
Rate limits are not concurrency limits
Two limits sound alike and are unrelated. **Concurrency** is how many calls may be *in flight* at once — a `Semaphore(5)…
Empezar lección
Continuation, COROUTINE_SUSPENDED y las etiquetas de estado que emite el compilador — y después el scheduler debajo: workers, colas locales y work stealing.
- Paso 89
Continuation and COROUTINE_SUSPENDED
Part I said `suspend` compiles to a state machine. This part takes the lid off, and the whole mechanism turns out to be …
Empezar lección - Paso 90
The state machine: labels and spilled locals
A suspending function with more than one suspension point compiles to **a class**. The class implements `Continuation`, …
Empezar lección - Paso 91
Interception: how a dispatcher gets its say
The previous two lessons left a gap: `resumeWith` is an ordinary method call, so how does a coroutine end up on a *parti…
Empezar lección - Paso 92
The scheduler underneath
`Dispatchers.Default` and `Dispatchers.IO` are two views of a single `CoroutineScheduler`. The thread names give it away…
Empezar lección - Paso 93
Local queues and work stealing
The previous lesson left the scheduler with per-worker local queues and a global one. That design has an obvious failure…
Empezar lección
La capa sobre la que se construyen las corrutinas: hilos, executors, ForkJoinPool, atómicos y compare-and-set, y las reglas happens-before que hacen visible el estado.
- Paso 94
Threads, pools and what coroutines replaced
Coroutines run on threads, so it is worth being precise about what a thread costs and which of those costs coroutines ac…
Empezar lección - Paso 95
ForkJoinPool, parallel streams and the shared-pool trap
`ForkJoinPool` is the JDK's work-stealing scheduler, and Part XVII's local queues, LIFO ordering and stealing are the sa…
Empezar lección - Paso 96
Atomics and compare-and-set
**Compare-and-set is the hardware primitive underneath every lock-free structure**: "if this location still holds the va…
Empezar lección - Paso 97
Happens-before and visibility
**A write on one thread is not automatically visible on another.** The compiler may hoist a field read out of a loop, th…
Empezar lección - Paso 98
Coroutine-safe is not thread-safe
**Thread-safe** means: correct when two threads touch it at the same instant. **Coroutine-safe** means: correct when one…
Empezar lección
Dónde viven los scopes en un código, qué capas pueden tener uno, los leaks que aparecen cuando no — y la checklist de revisión para todo eso.
- Paso 99
Leaks, scopes and runBlocking
Coroutine leaks come in two shapes, and both are easy to see once you know the test to apply: **can you name the lifetim…
Empezar lección - Paso 100
Where a scope belongs
The single most useful architectural rule in this whole course fits on one line: **a class may own a `CoroutineScope` if…
Empezar lección - Paso 101
Request scope and application scope
A server has at least two lifetimes running at once. The **request scope** begins when a request arrives and ends when t…
Empezar lección - Paso 102
Suspend functions are the boundary
Once components own scopes and everything else suspends, one question remains: **what may cross a boundary between two l…
Empezar lección - Paso 103
The footgun checklist
Everything in this course has been building toward a practical ability: reading a diff and seeing the concurrency bug in…
Empezar lección
Logging con correlation ids a través de puntos de suspensión, métricas con significado, y apagar sin perder trabajo en vuelo.
- Paso 104
Logging that survives a suspension point
Every JVM logging setup you have used carries request context in a **`ThreadLocal`** — SLF4J's MDC, Logback's `%X{reques…
Empezar lección - Paso 105
Metrics that mean something
You cannot operate what you cannot see, and a coroutine service is *less* visible than a thread-per-request one — thread…
Empezar lección - Paso 106
Graceful shutdown
`scope.cancel()` is not a shutdown, it is an abort. Fifty jobs half-way through a 200 ms operation, cancelled the moment…
Empezar lección - Paso 107
The Kubernetes pod lifecycle
The previous lesson's three-move shutdown is correct and, dropped into Kubernetes unchanged, it will still return 502s o…
Empezar lección - Paso 108
Health checks that check the right thing
The default health endpoint returns `200 OK` from whichever thread served the request. In a coroutine service that is al…
Empezar lección
Cuándo no paralelizar, dónde está el techo real, y cómo elegir un número de concurrencia que puedas defender con una medición.
- Paso 109
When not to parallelize
Twenty parts of this course have been about doing things concurrently. This one is about the cases where the honest answ…
Empezar lección - Paso 110
Where the ceiling actually is
"How many should I run in parallel?" is usually answered by doubling until it feels fast. There is a better way, and it …
Empezar lección - Paso 111
Choosing the number
The previous lesson gave you the tools — the knee, the retrograde curve, `L = λW`. This one is about applying them, and …
Empezar lección - Paso 112
When memory is the limit, not time
Every previous lesson in this part treated concurrency as a question about speed. It is also, and often first, a questio…
Empezar lección
Seis sistemas para construir que combinan todo: un agregador, un scraper acotado, una cola de trabajo, un event bus, un pipeline de streaming y una revisión final.
- Paso 113
Lab 1: the fan-out aggregator
The first lab is the shape almost every service eventually has: a page assembled from several backends, none of which yo…
Empezar lección - Paso 114
Lab 2: the bounded scraper
The second lab is a client for a resource that has opinions: a host that answers four requests at a time, allows twenty …
Empezar lección - Paso 115
Lab 3: the work queue
A `Channel` plus `repeat(n) { launch { for (m in inbox) … } }` is a worker pool in four lines, and Part VI built it. Thi…
Empezar lección - Paso 116
Lab 4: the in-process event bus
An in-process event bus looks like the easiest thing in this part and contains the sharpest trade-off. Start with the ty…
Empezar lección - Paso 117
Lab 5: the streaming pipeline
The last build-it lab is a four-stage pipeline: read, parse, enrich, write, at 5, 10, 20 and 8 milliseconds an item. For…
Empezar lección - Paso 118
Final capstone: a component that survives review
Everything in this course was aiming at one artefact: a component you would be comfortable shipping and comfortable hand…
Empezar lección
¿Quieres más repeticiones? Estos bancos de retos en Arena profundizan los operadores y patrones de este curso.