10 Ejercicios Básicos de Go para Principiantes
Domina los fundamentos de Go con 10 ejercicios prácticos: desde Hola Mundo hasta estructuras de datos. Código comentado y listo para ejecutar.
Go (o Golang) es uno de los lenguajes de backend de más rápido crecimiento: compila a binario nativo, tiene concurrencia integrada y una sintaxis sorprendentemente limpia. El problema es que muchos tutoriales te lanzan directamente a goroutines y channels sin consolidar los fundamentos.
En este artículo vas a practicar 10 ejercicios progresivos que cubren las bases reales del lenguaje: entrada/salida, tipos, control de flujo, funciones, recursión, slices y structs. Cada ejercicio incluye el código completo con comentarios explicativos para que entiendas por qué funciona, no solo qué hace.
Si ya completaste estos ejercicios y quieres dar el siguiente paso, revisa los proyectos de desarrollo backend a medida donde aplico Go en entornos de producción.
¿Por qué aprender Go con ejercicios prácticos?
Go fue diseñado en Google para resolver problemas reales de ingeniería: compilación rápida, rendimiento cercano a C y gestión de dependencias simple. Hoy es el lenguaje detrás de Docker, Kubernetes, Terraform y decenas de APIs de alto tráfico.
Aprender Go con ejercicios progresivos te da tres ventajas concretas:
- Memoria muscular: escribir código a mano fija mejor los patrones sintácticos que solo leerlos.
- Errores tempranos: Go tiene un compilador estricto que te enseña buenas prácticas desde el día uno (variables no usadas = error de compilación).
- Confianza incremental: pasar de
fmt.Printlna un struct con métodos en 10 pasos genera confianza real.
Nota: Para ejecutar cualquiera de estos ejercicios necesitas Go instalado. Verifica con
go versionen tu terminal. Si no lo tienes, descárgalo desde go.dev.
10 ejercicios básicos de Go resueltos
Ejercicio 1: Hola Mundo y saludo personalizado
El punto de partida de cualquier lenguaje. Este ejercicio muestra dos cosas fundamentales de Go: la función fmt.Println para imprimir y dos formas distintas de leer entrada del usuario.
¿Qué aprenderás?
- Estructura mínima de un programa Go (
package main,func main) fmt.Scanlnpara leer una palabrabufio.NewReaderpara leer líneas completas con espacios
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
// Imprimir hola mundo
fmt.Println("Hola, mundo")
// Método 1: fmt.Scanln (funciona bien para una sola palabra)
var name string
fmt.Print("Ingrese su nombre: ")
_, err := fmt.Scanln(&name)
if err != nil {
fmt.Println("Error al obtener nombre.")
}
fmt.Printf("Hola estimado %s\n", name)
// Método 2: bufio.Reader (recomendado para líneas con espacios)
reader := bufio.NewReader(os.Stdin)
fmt.Print("Ingrese su nombre: ")
name, err = reader.ReadString('\n')
if err != nil {
fmt.Println("Error al obtener nombre.")
}
// TrimSpace elimina espacios y saltos de línea en los extremos
name = strings.TrimSpace(name)
fmt.Printf("Hola estimado %s\n", name)
}
Tip: Prefiere
bufio.NewReadercuando el usuario pueda ingresar texto con espacios (nombres compuestos, frases).fmt.Scanlnse detiene en el primer espacio.
Ejercicio 2: Suma de dos números con entrada del usuario
Este ejercicio introduce la conversión de tipos (strconv.Atoi) y la extracción de funciones, un hábito clave en Go para mantener el código limpio.
¿Qué aprenderás?
- Convertir string a int con
strconv.Atoi - Devolver múltiples valores desde una función (patrón
value, error) - Manejo explícito de errores con
fmt.Errorf
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// leerNum encapsula la lectura y conversión de un número entero.
// Devuelve el entero y un error si la entrada es inválida.
func leerNum(reader *bufio.Reader, msg string) (int, error) {
fmt.Print(msg)
numString, err := reader.ReadString('\n')
if err != nil {
return 0, fmt.Errorf("error al momento de obtener el número")
}
numString = strings.TrimSpace(numString)
num, err := strconv.Atoi(numString)
if err != nil {
return 0, fmt.Errorf("el número ingresado es inválido")
}
return num, nil
}
func main() {
reader := bufio.NewReader(os.Stdin)
num1, err := leerNum(reader, "Ingrese el num 1: ")
if err != nil {
fmt.Printf("Num 1 - %s\n", err)
return
}
num2, err := leerNum(reader, "Ingrese el num 2: ")
if err != nil {
fmt.Printf("Num 2 - %s\n", err)
return
}
sum := num1 + num2
fmt.Printf("La suma de %d y %d es %d\n", num1, num2, sum)
}
Patrón clave de Go: devolver
(valor, error)en lugar de lanzar excepciones. Obliga a quien llama a decidir qué hacer con el error, lo que produce código más robusto.
Ejercicio 3: Mayor de tres números
Aquí practicamos condicionales y también introducimos los slices, la estructura de datos más usada en Go. Se presentan dos enfoques: el explícito con if/else y el genérico con range.
¿Qué aprenderás?
- Condicionales con
&& - Slices (
[]int) e iteración conrange strings.Joinpara construir texto desde una lista
package main
import (
"fmt"
"strconv"
"strings"
)
func main() {
// Enfoque 1: tres variables con if/else
num1, num2, num3 := 1, 4, 6
if num1 > num2 && num1 > num3 {
fmt.Printf("El mayor es %d\n", num1)
} else if num2 > num3 && num2 > num1 {
fmt.Printf("El mayor es %d\n", num2)
} else {
fmt.Printf("El mayor es %d\n", num3)
}
// Enfoque 2: slice de N números con range (más escalable)
numbers := []int{1, 2, 4, 23, 5, 6, 7}
mayor := numbers[0]
for _, num := range numbers {
if num > mayor {
mayor = num
}
}
// Construir lista de los demás números para el mensaje
otros := []string{}
for _, num := range numbers {
if num != mayor {
otros = append(otros, strconv.Itoa(num))
}
}
fmt.Printf(
"El número %d es mayor que: %s\n",
mayor,
strings.Join(otros, ", "),
)
}
Nota: el Enfoque 2 escala a cualquier cantidad de números sin cambiar la lógica, lo que lo hace más mantenible.
Ejercicio 4: Factorial con bucle y recursión
El factorial es el ejercicio clásico para entender la recursión. Go la soporta sin problema, pero también es ideal para ver cómo un bucle for resuelve el mismo problema de forma iterativa.
¿Qué aprenderás?
- Bucle
forconrangesobre un entero (Go 1.22+) - Definir y llamar funciones recursivas
- Cuándo preferir iteración vs recursión
package main
import "fmt"
// recursion calcula el factorial de n de forma recursiva.
// Caso base: factorial de 0 o 1 es 1.
func recursion(num int) int {
if num == 1 || num == 0 {
return 1
}
return num * recursion(num-1)
}
func main() {
numberFac := 4
// Enfoque iterativo con bucle for
mul := 1
for num := range numberFac + 1 {
if num != 0 {
mul = mul * num
}
}
fmt.Printf("Factorial de %d (bucle): %d\n", numberFac, mul)
fmt.Printf("Factorial de %d (recursión): %d\n", numberFac, recursion(numberFac))
}
¿Cuándo usar recursión en Go? Para árboles, grafos o problemas con estructura naturalmente recursiva. Para cálculos simples como el factorial, el bucle es más eficiente porque evita el overhead de llamadas en la pila de ejecución.
Ejercicio 5: Invertir una cadena
Invertir un string parece trivial hasta que te das cuenta de que en Go un string es una secuencia de bytes, no de caracteres. Para manejar correctamente tildes y caracteres Unicode usamos []rune.
¿Qué aprenderás?
- Diferencia entre
string,[]bytey[]rune - Recursión con slices de runas
- Conversión
string↔rune
package main
import "fmt"
// revertText recorre el slice de runas de atrás hacia adelante usando recursión.
func revertText(arr []rune, lenArr int) string {
if lenArr == 0 {
return ""
}
// Toma el último carácter y concatena con el resultado del resto
return string(arr[lenArr-1]) + revertText(arr, lenArr-1)
}
func main() {
myText := "Hola amigo"
// Convertir a []rune para manejar correctamente tildes y Unicode
runas := []rune(myText)
fmt.Println(revertText(runas, len(runas)))
// Output: ogima aloH
}
Regla de oro: siempre usa
[]runecuando iteres sobre un string que puede contener caracteres no-ASCII (español, emojis, etc.). Iterar sobrestringdirectamente te da bytes, no caracteres.
Ejercicio 6: Contar vocales en un texto
Un ejercicio que combina iteración sobre strings con una verificación simple. Vemos dos formas de hacerlo: lista manual y strings.Contains.
¿Qué aprenderás?
- Iteración sobre un string con índices
strings.ToLowerpara normalizarstrings.Containscomo alternativa elegante aif a == "a" || a == "e"...
package main
import (
"fmt"
"strings"
)
func main() {
text := "abcdaewAOiiII"
totalCount := 0
for idx := range text {
// Normalizar a minúsculas para cubrir A, E, I, O, U también
char := strings.ToLower(string(text[idx]))
// strings.Contains verifica si "aeiou" contiene el carácter actual
if strings.Contains("aeiou", char) {
totalCount++
}
}
fmt.Printf("El total de vocales en \"%s\" es: %d\n", text, totalCount)
// Output: El total de vocales en "abcdaewAOiiII" es: 7
}
Alternativa con función propia: si necesitas lógica más compleja (vocales con tilde: á, é, í…) conviene crear una función
esVocal(r rune) boolque incluya el mapa completo de vocales acentuadas.
Ejercicio 7: Tabla de multiplicar
Simple pero esencial para consolidar el bucle for en Go y el formateo con fmt.Printf.
¿Qué aprenderás?
fmt.Scanlnpara leer un entero directamente- Bucle
forcon índice clásico - Formateo de tabla con
Printf
package main
import "fmt"
func main() {
var num int
fmt.Print("Ingrese un número: ")
fmt.Scanln(&num)
fmt.Printf("\n--- Tabla de multiplicar del %d ---\n", num)
for i := 1; i <= 10; i++ {
fmt.Printf("%d x %2d = %d\n", num, i, num*i)
}
}
Salida esperada para num = 3:
--- Tabla de multiplicar del 3 ---
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
...
3 x 10 = 30
Ejercicio 8: Calculadora de áreas geométricas
Introducimos switch/case y el paquete math para cálculos geométricos. El switch de Go no necesita break explícito (no hay fall-through por defecto).
¿Qué aprenderás?
switchsinbreakexplícito- Importar y usar
math.Piymath.Pow - Variables
float64para operaciones decimales
package main
import (
"fmt"
"math"
)
func main() {
var opcion int
fmt.Println("Seleccione la figura:")
fmt.Println("1. Círculo")
fmt.Println("2. Rectángulo")
fmt.Println("3. Triángulo")
fmt.Print("Opción: ")
fmt.Scanln(&opcion)
switch opcion {
case 1:
var radio float64
fmt.Print("Ingrese el radio: ")
fmt.Scanln(&radio)
// Fórmula: π * r²
area := math.Pi * math.Pow(radio, 2)
fmt.Printf("Área del círculo: %.2f\n", area)
case 2:
var base, altura float64
fmt.Print("Ingrese la base: ")
fmt.Scanln(&base)
fmt.Print("Ingrese la altura: ")
fmt.Scanln(&altura)
area := base * altura
fmt.Printf("Área del rectángulo: %.2f\n", area)
case 3:
var base, altura float64
fmt.Print("Ingrese la base: ")
fmt.Scanln(&base)
fmt.Print("Ingrese la altura: ")
fmt.Scanln(&altura)
// Fórmula: (base * altura) / 2
area := (base * altura) / 2
fmt.Printf("Área del triángulo: %.2f\n", area)
default:
fmt.Println("Opción inválida")
}
}
Diferencia con otros lenguajes: en Go el
switchhace comparación exacta de valor y no tiene fall-through automático. Si necesitas fall-through explícito, usa la palabra clavefallthrough.
Ejercicio 9: Conversor de temperaturas
Este ejercicio consolida el uso de funciones con parámetros y valores de retorno, más el switch nuevamente para manejar opciones del menú.
¿Qué aprenderás?
- Funciones con parámetros
float64 - Fórmulas matemáticas en funciones separadas
- Organización del código por responsabilidad
package main
import "fmt"
// celsiusToFahrenheit convierte grados Celsius a Fahrenheit.
func celsiusToFahrenheit(c float64) float64 {
return (c * 9 / 5) + 32
}
// fahrenheitToCelsius convierte grados Fahrenheit a Celsius.
func fahrenheitToCelsius(f float64) float64 {
return (f - 32) * 5 / 9
}
func main() {
var opcion int
fmt.Println("Conversor de temperaturas:")
fmt.Println("1. Celsius → Fahrenheit")
fmt.Println("2. Fahrenheit → Celsius")
fmt.Print("Opción: ")
fmt.Scanln(&opcion)
switch opcion {
case 1:
var c float64
fmt.Print("Ingrese grados Celsius: ")
fmt.Scanln(&c)
fmt.Printf("%.2f °C = %.2f °F\n", c, celsiusToFahrenheit(c))
case 2:
var f float64
fmt.Print("Ingrese grados Fahrenheit: ")
fmt.Scanln(&f)
fmt.Printf("%.2f °F = %.2f °C\n", f, fahrenheitToCelsius(f))
default:
fmt.Println("Opción inválida")
}
}
Ejercicio 10: Lista de tareas pendientes (CLI)
El ejercicio más completo de la serie. Introduce structs, slices de structs y funciones que transforman el estado. Es una mini-arquitectura que refleja cómo se organiza el código Go real.
¿Qué aprenderás?
- Definir y usar
structcon campos tipados - Modificar slices pasándolos a funciones que devuelven el slice actualizado
- Separar responsabilidades en funciones: crear, listar, marcar
package main
import "fmt"
// TTodo representa una tarea con ID, nombre, descripción y estado.
type TTodo struct {
TodoID int
TodoName string
TodoDesc string
TodoStatus bool
}
// newTodo agrega una nueva tarea al slice con ID autoincremental.
func newTodo(todos []TTodo, nueva TTodo) []TTodo {
latestIdx := 1
if len(todos) > 0 {
lastItem := todos[len(todos)-1]
if lastItem.TodoID > 0 {
latestIdx = lastItem.TodoID + 1
}
}
nueva.TodoID = latestIdx
return append(todos, nueva)
}
// listTodos imprime todas las tareas con su estado actual.
func listTodos(todos []TTodo) {
fmt.Println("===== Lista de tareas =====")
for idx, item := range todos {
statusStr := "pendiente"
if item.TodoStatus {
statusStr = "✓ completado"
}
fmt.Printf(
"[%d] ID:%d | %s | %s | %s\n",
idx+1, item.TodoID, item.TodoName, item.TodoDesc, statusStr,
)
}
fmt.Println("===========================")
}
// markCompleted busca la tarea por ID y la marca como completada.
func markCompleted(todos []TTodo, taskID int) []TTodo {
for idx, item := range todos {
if item.TodoID == taskID {
todos[idx].TodoStatus = true
break
}
}
return todos
}
func main() {
myTodos := []TTodo{}
myTodos = newTodo(myTodos, TTodo{TodoName: "Aprender structs", TodoDesc: "Estudiar tipos compuestos en Go"})
myTodos = newTodo(myTodos, TTodo{TodoName: "Practicar slices", TodoDesc: "Ejercicios con append y range"})
myTodos = newTodo(myTodos, TTodo{TodoName: "Escribir tests", TodoDesc: "Usar el paquete testing"})
fmt.Println("--- Estado inicial ---")
listTodos(myTodos)
myTodos = markCompleted(myTodos, 1)
fmt.Println("\n--- Después de completar la tarea 1 ---")
listTodos(myTodos)
}
Salida esperada:
--- Estado inicial ---
===== Lista de tareas =====
[1] ID:1 | Aprender structs | Estudiar tipos compuestos en Go | pendiente
[2] ID:2 | Practicar slices | Ejercicios con append y range | pendiente
[3] ID:3 | Escribir tests | Usar el paquete testing | pendiente
===========================
--- Después de completar la tarea 1 ---
===== Lista de tareas =====
[1] ID:1 | Aprender structs | Estudiar tipos compuestos en Go | ✓ completado
[2] ID:2 | Practicar slices | Ejercicios con append y range | pendiente
[3] ID:3 | Escribir tests | Usar el paquete testing | pendiente
===========================
Conceptos clave aprendidos en estos 10 ejercicios
| Ejercicio | Concepto principal | Paquetes utilizados |
|---|---|---|
| 1 - Hola Mundo | fmt, bufio, entrada de usuario | fmt, bufio, os, strings |
| 2 - Suma | Funciones, errores, conversión de tipos | strconv, fmt, bufio |
| 3 - Mayor | Slices, range, strings.Join | strconv, strings |
| 4 - Factorial | Recursión vs iteración | fmt |
| 5 - Invertir string | Runas, Unicode, recursión | fmt |
| 6 - Vocales | strings.Contains, normalización | strings |
| 7 - Tabla | Bucle for clásico, Printf | fmt |
| 8 - Áreas | switch, math.Pi, math.Pow | math, fmt |
| 9 - Conversor | Funciones con float64 | fmt |
| 10 - Tareas CLI | Structs, slices de structs, métodos | fmt |
Mejores prácticas que refuerzan estos ejercicios
-
Maneja siempre los errores: Go obliga a declarar el error y el compilador advierte si lo ignoras. El patrón
valor, err := func()seguido deif err != niles idiomático y no es opcional. -
Nombre descriptivos para funciones:
leerNum,markCompleted,revertTextson más claros quefn1oprocess. Go valora la claridad sobre la brevedad en los nombres. -
Evita variables no usadas: Go lanza error de compilación si declaras una variable y no la usas. Es una feature, no un bug: elimina código muerto desde el principio.
-
Prefiere
[]runesobre[]bytepara texto: cuando trabajes con strings que pueden tener caracteres no-ASCII (español, japonés, emojis), convierte a[]runepara no corromper los caracteres al iterar. -
Funciones pequeñas con una responsabilidad: los ejercicios 2 y 10 muestran cómo extraer
leerNumonewTodohace elmainlegible y las funciones fáciles de testear de forma aislada.
Conclusión
Estos 10 ejercicios cubren el núcleo fundamental de Go: tipos, control de flujo, funciones, errores, slices y structs. Con estos bloques puedes construir desde scripts CLI hasta APIs REST básicas.
El siguiente nivel natural es trabajar con goroutines y channels (la concurrencia nativa de Go), interfaces y el paquete net/http para construir servicios web. Pero sin estos fundamentos sólidos, esos temas avanzados se vuelven confusos.
¿Estás construyendo un backend en Go para tu empresa o startup? Cuéntame tu proyecto aquí y te respondo en menos de 24 horas.
¿Tienes un proyecto en mente?
Convierte tu idea en un producto real
Desarrollo web, aplicaciones a medida y consultoría tecnológica para empresas y startups. Cuéntame tu proyecto y te respondo en menos de 24 horas.