break y continue en forEach de Kotlin: cómo funciona bajo el capó

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Por qué break y continue no funcionan en forEach de Kotlin (y cómo solucionarlo)

\n\n

Si alguna vez has programado en Kotlin y has intentado salir prematuramente de un bucle forEach usando break, lo más probable es que te hayas encontrado con un subrayado rojo en el IDE y un error de compilación. ¿Te suena? No estás solo: esta es una de las preguntas más populares sobre Kotlin en Stack Overflow, con miles de visitas. En este artículo no solo mostraremos cómo sortear la limitación, sino que también exploraremos bajo el capó: por qué está diseñado así, cómo funciona return dentro de una lambda y qué alternativas existen. ¡Allá vamos!

\n\n

Ejemplo clásico: el error del principiante

\n\n

Imagina que tienes una lista de números y necesitas mostrar solo los primeros tres elementos. Intuitivamente, querrías escribir algo así:

\n\n
val numbers = listOf(1, 2, 3, 4, 5)\nnumbers.forEach {\n    println(it)\n    if (it == 3) break // ¡Error de compilación!\n}\n
\n\n

El compilador mostrará un error: ‘break’ and ‘continue’ are only allowed inside a loop. ¿Por qué? Después de todo, forEach parece un bucle. La respuesta está en cómo está implementado realmente forEach.

\n\n

Anatomía de forEach: no es un bucle, es una llamada a función

\n\n

Abramos la biblioteca estándar de Kotlin y observemos la firma de forEach:

\n\n
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {\n    for (element in this) action(element)\n}\n
\n\n

La palabra clave aquí es inline. Esto significa que, en tiempo de compilación, el cuerpo de la función se inserta directamente en el lugar de la llamada. Pero aun así, forEach es una función de orden superior que recibe otra función (una lambda) como argumento. Y una lambda no es un bloque de código de bucle, es un objeto-función normal. break y continue solo tienen sentido dentro de construcciones for, while o do-while, no dentro de una función arbitraria.

\n\n

En otras palabras, cuando escribes numbers.forEach { ... }, no estás creando un bucle, estás llamando a un método que internamente usa un bucle for. El compilador no puede "pasar" un break desde tu lambda al bucle externo porque la lambda es una entidad separada. No es un error, es un diseño.

\n\n

Cómo funciona return en forEach (un giro inesperado)

\n\n

Ahora bien, return dentro de forEach funciona... pero no como esperas. Observa:

\n\n
fun main() {\n    val numbers = listOf(1, 2, 3, 4, 5)\n    numbers.forEach {\n        if (it == 3) return // ¡Sale de main()!\n        println(it)\n    }\n    println("Esta línea no se ejecutará")\n}\n
\n\n

La salida será: 1 2. Aquí, return es un non-local return (retorno no local). Finaliza no solo la lambda, sino también toda la función externa (main). Esto funciona gracias a que forEach está declarado como inline. El compilador inserta el código de la lambda directamente en el cuerpo de main, y return pasa a referirse a la fun declarada más cercana, es decir, main.

\n\n

Si quieres salir solo de forEach y no de toda la función, usa una etiqueta (label):

\n\n
fun main() {\n    val numbers = listOf(1, 2, 3, 4, 5)\n    numbers.forEach loop@{\n        if (it == 3) return@loop\n        println(it)\n    }\n    println("Esta línea se ejecutará")\n}\n
\n\n

Salida: 1 2 y Esta línea se ejecutará. La etiqueta loop@ le dice al compilador: "sal de la lambda, pero no de main". Básicamente, es un análogo de continue: saltamos la iteración actual y pasamos al siguiente elemento.

\n\n

Cómo emular break y continue en forEach<

Blogs

Book Recommendations