Go tiene exactamente una palabra clave para bucles: for. Tiene cuatro formas, y entre todas cubren cualquier bucle que escribirías con for, while, do while o foreach en otros lenguajes.
La cabecera del bucle no lleva paréntesis, y las llaves son siempre obligatorias.
El bucle de tres partes
for init; condition; post {
// body
}
initse ejecuta una vez antes del bucle. Suele ser una declaración corta de variable comoi := 0, y esa variable solo existe dentro del bucle.conditionse comprueba antes de cada iteración. Cuando es falsa, el bucle termina.- Se ejecuta el cuerpo.
postse ejecuta después de cada iteración, normalmentei++.
En Go i++ es una sentencia, no una expresión, así que post no puede contener i++, j--. Usa una asignación paralela: i, j = i+1, j-1.
Las tres partes son opcionales. for ; i < 10; {} es válido, y gofmt lo reescribe como for i < 10 {}, que es la siguiente forma.
Bucle con solo condición (el while de Go)
Con solo una condición, for se comporta como while en otros lenguajes:
No hay un do while aparte. La página del bucle while muestra cómo conseguir el comportamiento de "ejecutar al menos una vez".
Bucles infinitos
Un for sin nada detrás se repite para siempre. Sal de él con break, return, os.Exit o un panic:
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
process(line)
}
Es la forma estándar para servidores, workers y bucles de entrada. Un for {} con el cuerpo vacío consume un núcleo de CPU entero; en código real, el cuerpo se bloquea en algo (una lectura, una recepción de un channel, un select).
Bucles range
range itera sobre una colección y produce uno o dos valores por iteración. Qué son depende de lo que recorras:
| Recorres | Primer valor | Segundo valor |
|---|---|---|
| slice o array | índice | elemento (una copia) |
| string | índice en bytes | rune (carácter Unicode) |
| map | clave | valor |
| channel | elemento | (ninguno) |
entero n (Go 1.22) | 0 a n menos 1 | (ninguno) |
| función iteradora (Go 1.23) | lo que produce la función | lo que produce la función |
Tres detalles que muestra la salida:
- La variable del valor es una copia.
v *= 2dentro del bucle no cambiaría el slice; para modificar elementos, indexa el slice connums[i], como hace el tercer bucle. - El índice de un string es un desplazamiento en bytes.
éocupa dos bytes, así que el índice salta de 1 a 3. - El orden de un map es aleatorio a propósito. Go lo aleatoriza para que el código no pueda depender de él.
slices.Sorted(maps.Keys(m))(Go 1.23) te da las claves en orden.
range se explica completo, con channels, funciones iteradoras y las reglas de copia, en la página de range.
break y continue
break termina el bucle más interno. continue salta a la siguiente iteración (ejecutando antes post, en un bucle de tres partes):
break y continue con etiqueta
Un break a secas solo sale del bucle más interno. Para salir de un bucle exterior desde dentro de uno anidado, ponle una etiqueta al bucle exterior:
Una etiqueta es un identificador seguido de dos puntos, colocado justo antes del for. Go trata una etiqueta sin usar como un error de compilación, igual que una variable sin usar.
break dentro de switch y select
Dentro de un for, un break en un caso de switch o select sale del switch o del select, no del bucle:
for {
switch cmd := next(); cmd {
case "quit":
break // only exits the switch; the loop keeps running
}
}
Usa un break con etiqueta, o return, para salir del bucle desde dentro de un switch o un select.
Variables de bucle desde Go 1.22
Go 1.22 cambió el ámbito de las variables de bucle: cada iteración recibe ahora una copia nueva de i y v. Antes se reutilizaba una sola variable durante todo el bucle, lo que rompía las closures y goroutines que la capturaban:
Con Go 1.22 y posteriores esto imprime 0 1 2 y [aa bb cc]. Con la semántica antigua, el mismo programa escrito con for i := 0; i < 3; i++ (range sobre un entero no existía antes de 1.22) imprimía 3 3 3, y las goroutines competían por las variables compartidas i y name. El apaño antiguo, i := i al principio del cuerpo del bucle, ya no hace falta.
El comportamiento lo decide cada módulo con la línea go de go.mod, no la versión del compilador. Un módulo que todavía dice go 1.21 mantiene la semántica antigua aunque se compile con Go 1.24.
Errores comunes
Modificar la copia. for _, v := range items { v.Count++ } cambia una copia. Usa items[i].Count++, o recorre un slice de punteros.
Añadir elementos mientras recorres. range evalúa el slice una vez, al principio. Los elementos añadidos dentro del bucle no se visitan, así que el bucle termina igualmente, pero no verá los nuevos.
Borrar de un slice por índice en un bucle hacia delante. Quitar el elemento i desplaza el resto a la izquierda, así que el siguiente elemento se salta. Construye un slice nuevo, recorre hacia atrás o usa slices.DeleteFunc.
Desplazamiento por uno con <=. for i := 0; i <= len(s); i++ lee s[len(s)] en la última pasada y provoca un panic con index out of range. Usa <, o mejor, range.
Trabajo pesado en la condición. La condición se ejecuta en cada iteración. for i := 0; i < expensiveCount(); i++ llama a la función cada vez; calcúlala una sola vez antes del bucle.
Usar un float como contador. for x := 0.0; x != 1.0; x += 0.1 nunca termina, porque 0.1 no tiene representación binaria exacta. Cuenta con un entero y calcula el float a partir de él.
Preguntas frecuentes
¿Cómo se escribe un bucle for en Go?
La forma clásica tiene tres partes separadas por punto y coma y sin paréntesis:
for i := 0; i < 5; i++ {
fmt.Println(i)
}
Desde Go 1.22 también puedes escribir for i := range 5, que cuenta de 0 a 4. Para recorrer un slice, usa for i, v := range items.
¿Tiene Go un bucle while?
No existe la palabra clave while, pero un for con solo una condición es un bucle while: for n > 0 { n /= 2 }. for { ... } sin condición es un bucle infinito del que sales con break o return.
¿Cómo recorro un map en Go?
for key, value := range m { ... }. El orden de iteración no está especificado, y el runtime lo aleatoriza, así que puede cambiar de un bucle a otro. Nunca dependas de él. Para un orden estable, reúne las claves, ordénalas con slices.Sort y recorre las claves ordenadas.
¿Cómo salgo de un bucle anidado en Go?
Pon una etiqueta antes del bucle exterior y nómbrala en el break: outer: for ... { for ... { if found { break outer } } }. Un break a secas solo sale del for, switch o select más interno. continue outer salta del mismo modo a la siguiente iteración del bucle etiquetado.
¿Qué cambió en las variables de bucle en Go 1.22?
Antes de Go 1.22, un bucle for tenía una sola variable compartida por todas las iteraciones, así que las closures y goroutines lanzadas en el bucle solían ver todas el valor final. Desde Go 1.22 cada iteración tiene su propia copia, así que capturar i o v en una closure funciona como esperas. El nuevo comportamiento se aplica cuando el go.mod del módulo indica go 1.22 o posterior.