Una entrevista de Go para un puesto sénior no es un examen de sintaxis. A nadie que contrate a un ingeniero sénior le importa si sabes recitar el bucle for de tres cláusulas o si recuerdas que append puede reservar memoria nueva. Quieren saber si eres capaz de localizar una fuga de goroutines, defender cómo manejas los errores y diseñar una API con la que el equipo pueda convivir durante dos años. Las preguntas son abiertas a propósito. Quien te entrevista observa cómo piensas, no comprueba si te has memorizado la biblioteca estándar.
Esta guía cubre los temas que distinguen a los candidatos sénior de los de nivel intermedio, con código ejecutable para cada uno. Se basa en lo que los desarrolladores de Go dicen que construyen y en lo que les cuesta, no en listas de «las 50 preguntas más frecuentes» sacadas de internet.
Resumen rápido
- La concurrencia es donde más rinde la preparación. Lo que más construyen los desarrolladores de Go son servicios de API/RPC (75 %) y herramientas de línea de comandos (62 %), y ambos dependen mucho de goroutines, channels y
context(Encuesta de desarrolladores de Go 2024, segundo semestre). - El mayor reto que se reporta es escribir código idiomático, no la falta de funcionalidades. El 33 % de los encuestados señaló «asegurar que nuestro código Go sigue las buenas prácticas y las convenciones idiomáticas» como su mayor reto, más de los que citaron cualquier funcionalidad ausente del lenguaje (Encuesta de desarrolladores de Go 2025).
- Las carreras de datos (data races) ocurren en producción, así que cuenta con encontrarlas en la entrevista. El race detector de Uber encontró unas 2.000 carreras de datos en su monorepo de Go, y 210 ingenieros corrigieron alrededor de 1.100 de ellas en seis meses (Uber Engineering).
- Domina
errors.Is,errors.Asy%wal dedillo. El 28 % de los encuestados dijo que a Go le falta una funcionalidad que valoraba en otro lenguaje, y el manejo de errores encabeza esa lista, así que los entrevistadores indagan en cómo lo gestionas (Encuesta de desarrolladores de Go 2025). - Los genéricos pueden salir en la entrevista, pero también son una trampa. Llegaron en Go 1.18, y el equipo de Go los llamó «el mayor cambio de la historia del lenguaje» (blog de Go). Lo que demuestra nivel sénior es saber cuándo no usarlos.
- Trabaja con la toolchain actual. Go 1.26 es la última versión estable (notas de la versión Go 1.26). Los entrevistadores se dan cuenta cuando describes el comportamiento de una versión antigua.
Índice
- Qué evalúa realmente una entrevista sénior de Go
- Concurrencia: el núcleo de toda entrevista sénior de Go
- Context: cancelación y deadlines
- Manejo de errores al estilo sénior
- Genéricos: cuándo recurrir a ellos
- Interfaces y diseño de APIs
- La ronda de live coding: implementa un rate limiter
- Preguntas frecuentes
- Empieza tu camino hacia el nivel sénior en Go
Qué evalúa realmente una entrevista sénior de Go
No existe ninguna encuesta de referencia que clasifique «las preguntas más comunes en entrevistas de Go», y deberías desconfiar de cualquier blog que dé un porcentaje exacto. Lo que sí puedes hacer es partir de aquello en lo que los desarrolladores de Go dedican su tiempo. La encuesta de 2024 reveló que el 75 % de los encuestados construye servicios de API o RPC y el 62 % construye herramientas de línea de comandos (Encuesta de desarrolladores de Go 2024, segundo semestre). Es un trabajo concurrente, dependiente de la red y lleno de formas de fallar, y por eso las entrevistas vuelven una y otra vez a la concurrencia, context y el manejo de errores.
La encuesta de 2025 preguntó por los mayores retos. El 33 % mencionó seguir las buenas prácticas y las convenciones idiomáticas. El 28 % mencionó una funcionalidad que valoraba en otro lenguaje y que Go no tiene, sobre todo el manejo de errores, los tipos suma y la seguridad frente a punteros nil. El 26 % señaló encontrar módulos fiables (Encuesta de desarrolladores de Go 2025). Buena parte de una entrevista sénior consiste en que la empresa compruebe si has interiorizado las convenciones idiomáticas que a un tercio de la comunidad le cuestan.
Así que la entrevista evalúa sobre todo el criterio. ¿Eliges la primitiva de concurrencia adecuada o lanzas una goroutine por reflejo? ¿Puedes explicar por qué envolviste un error en lugar de registrarlo y seguir adelante? ¿Puedes decir «aquí no usaría genéricos» y defenderlo?
Concurrencia: el núcleo de toda entrevista sénior de Go
La concurrencia es donde los candidatos de nivel intermedio pierden la oferta. El error clásico es lanzar una goroutine por cada unidad de trabajo, sin límite, sin camino para los errores y sin forma de detenerla. Los candidatos sénior parten de un patrón acotado.
Las goroutines son baratas, y eso es lo que hace tentador el enfoque ingenuo. Según las FAQ de Go:
La pila inicial ronda los 2KB en la mayoría de plataformas y la gestiona el runtime, por eso un programa puede ejecutar cientos de miles de goroutines a la vez. Pero barato no significa gratis. Las goroutines sin límite consumen memoria, saturan los servicios de los que dependes y ocultan fugas. La solución habitual es un worker pool.
Worker pools
Un worker pool (grupo fijo de trabajadores) lanza un número fijo de goroutines y les pasa el trabajo a través de un channel. Es la respuesta estándar a «procesa 10.000 URLs sin abrir 10.000 sockets a la vez».
example.gogopackage main import ( "fmt" "sync" ) // fetchStatus stands in for any bounded I/O call (HTTP, DB, RPC). func fetchStatus(url string) string { return "200 " + url } func main() { urls := []string{"a", "b", "c", "d", "e"} const workers = 3 jobs := make(chan string) results := make(chan string) var wg sync.WaitGroup for range workers { wg.Go(func() { for url := range jobs { results <- fetchStatus(url) } }) } // Close results once every worker has returned. go func() { wg.Wait() close(results) }() go func() { for _, url := range urls { jobs <- url } close(jobs) }() for r := range results { fmt.Println(r) } }
Los entrevistadores se fijan en tres detalles. Cierras jobs para que terminen los bucles range de los workers. Usas un sync.WaitGroup para saber cuándo es seguro cerrar results. Y cierras results desde una goroutine aparte para que el bucle principal pueda vaciarlo. Si aciertas en eso, has demostrado que entiendes el conjunto. Dos detalles más recientes muestran que sigues la evolución de la toolchain. for range workers recorre un entero, algo que funciona desde Go 1.22. wg.Go, añadido en Go 1.25, sustituye a la antigua pareja wg.Add(1) y defer wg.Done(), y así desaparece el bug más común con WaitGroup, que es olvidar una de las dos llamadas.
Carreras de datos y el race detector
Cuenta con una pregunta del tipo «¿qué falla en este código?» con una carrera de datos dentro. Las carreras aparecen constantemente en código de producción, no solo en entrevistas. Uber publicó las cifras. Su monorepo de Go tiene unos 50 millones de líneas de código repartidas en cerca de 2.100 servicios de Go distintos. Su race detector encontró cerca de 2.000 carreras de datos, y 210 ingenieros corrigieron alrededor de 1.100 en seis meses (Uber Engineering).
La carrera de datos de manual es un contador compartido:
example.gogo// BROKEN: concurrent writes to count are a data race. func countBroken(items []int) int { count := 0 var wg sync.WaitGroup for _, n := range items { wg.Add(1) go func() { defer wg.Done() if n%2 == 0 { count++ // unsynchronized write } }() } wg.Wait() return count }
La solución correcta depende de la forma del problema. Un contador simple pide sync/atomic o un sync.Mutex. Para agregar resultados suele funcionar mejor un channel, donde cada goroutine informa de su resultado y una sola goroutine es dueña del total. Una respuesta sénior nombra el compromiso. Un mutex es más fácil de leer, las operaciones atómicas son más rápidas con contención y un channel («compartir memoria comunicando») elimina por completo el estado compartido. Después di «lo ejecutaría con go test -race», porque ese hábito detecta las carreras antes de que lleguen a producción.
Fan-out y fan-in
El fan-out reparte el trabajo entre varias goroutines y el fan-in combina sus resultados de nuevo en un único channel. Lo usarías para E/S en paralelo que alimenta a un solo consumidor, y encaja de forma natural con el worker pool anterior. Los candidatos suelen tropezar en el paso de combinación. Cerrar el channel combinado requiere un WaitGroup sobre los productores, igual que el channel results del worker pool. Si sabes explicar por qué ambos patrones cierran su salida de la misma forma, demuestras que entiendes quién es dueño de cada channel y que no te limitas a repetir un fragmento de código.
Context: cancelación y deadlines
Los servicios de Go pasan context.Context a lo largo de toda la pila de llamadas, y los entrevistadores te pedirán que lo uses correctamente. Las reglas son que context va como primer parámetro, se llama ctx y nunca se guarda en un struct. Transporta la cancelación, los deadlines y los valores asociados a la petición a través de los límites de la API.
La pregunta de seguimiento más habitual es «¿cómo detienes un worker que está bloqueado en una llamada lenta?». Haces un select sobre el trabajo y sobre ctx.Done():
example.gogofunc process(ctx context.Context, jobs <-chan string) error { for { select { case <-ctx.Done(): return ctx.Err() // context.Canceled or DeadlineExceeded case job, ok := <-jobs: if !ok { return nil // channel closed, work done } if err := handle(ctx, job); err != nil { return fmt.Errorf("handling %q: %w", job, err) } } } }
Devolver ctx.Err() le dice a quien llama por qué te detuviste, para que pueda distinguir un deadline vencido de una cancelación deliberada. Las respuestas más flojas ignoran el context o solo lo comprueban al principio del bucle. Entonces una goroutine atascada dentro de handle nunca se entera de la cancelación. Los buenos candidatos también mencionan que el código padre tiene que llamar a la función cancel que devuelven context.WithCancel o context.WithTimeout, normalmente con defer cancel(), para liberar recursos.
Ve preparado para «¿qué diferencia hay entre context.WithCancel y context.WithTimeout?». El primero cancela cuando llamas a cancel. El segundo cancela también cuando vence un deadline. En ambos casos deberías usar defer cancel(), porque dejar sin liberar la goroutine interna de un context también es una fuga de recursos.
Manejo de errores al estilo sénior
El manejo de errores es lo que más echan en falta de otros lenguajes los desarrolladores de Go. En 2025, el 28 % de los encuestados dijo que a Go le faltaba una funcionalidad que valoraba en otros lenguajes, y el manejo de errores encabezaba esa lista (Encuesta de desarrolladores de Go 2025). Esa frustración es la razón por la que los entrevistadores lo evalúan. Quieren ver que tratas los errores como valores que significan algo, no como ruido que se registra y se olvida.
Lo básico es envolver con %w para que quien llama pueda inspeccionar la cadena:
example.gogovar ErrNotFound = errors.New("not found") func loadUser(ctx context.Context, id string) (*User, error) { row, err := db.Query(ctx, id) if err != nil { // Wrap, don't replace. The caller can still see the root cause. return nil, fmt.Errorf("loadUser %s: %w", id, err) } if row == nil { return nil, fmt.Errorf("loadUser %s: %w", id, ErrNotFound) } return row, nil }
Después, errors.Is compara con un valor centinela y errors.As desenvuelve hasta un tipo concreto:
example.gogouser, err := loadUser(ctx, id) if errors.Is(err, ErrNotFound) { http.Error(w, "user not found", http.StatusNotFound) return } var validationErr *ValidationError if errors.As(err, &validationErr) { http.Error(w, validationErr.Field+" is invalid", http.StatusBadRequest) return }
Ve preparado para explicar cuándo usar cada uno. Un centinela con errors.Is encaja cuando a quien llama solo le importa saber qué error ocurrió. Un error tipado con errors.As encaja cuando necesita datos del error, como el nombre de un campo o un código de estado. Envuelve con %w solo cuando el error envuelto forma parte de verdad del contrato de tu API, porque envolver en exceso filtra detalles de implementación. Di también en voz alta que panic es para bugs del programador y fallos irrecuperables durante el arranque, no para condiciones esperadas como un registro inexistente. Usar panic para un error normal es una forma rápida de suspender una entrevista sénior.
Genéricos: cuándo recurrir a ellos
Los genéricos llegaron en Go 1.18. Así los anunció el equipo de Go:
Los entrevistadores preguntan por los genéricos para ver si sabes contenerte. La respuesta honesta es que la mayoría del código no los necesita y que los paquetes slices y maps de la biblioteca estándar ya cubren los casos habituales. Usa parámetros de tipo cuando, de lo contrario, tendrías que copiar la misma lógica para varios tipos o recurrir a interface{} y perder la seguridad de tipos.
Un buen uso es una utilidad con restricciones de tipo que la biblioteca estándar no te da ya hecha:
example.gogoimport "cmp" // Clamp constrains v to the range [lo, hi] for any ordered type. func Clamp[T cmp.Ordered](v, lo, hi T) T { if v < lo { return lo } if v > hi { return hi } return v }
La restricción cmp.Ordered del paquete estándar cmp limita T a los tipos que admiten < y >. El ejemplo que elijas también dice algo de ti. No escribas un Max genérico. Go tiene las funciones integradas max y min desde la 1.21, y slices.Max y slices.Min se encargan de los slices, así que una versión hecha a mano sugiere que usas genéricos por costumbre. Clamp justifica su parámetro de tipo porque nada en la biblioteca estándar hace lo mismo. Antes de los genéricos, una función así necesitaba una copia por tipo o interface{} con aserciones de tipo que podían provocar un panic en tiempo de ejecución. Ahora lo comprueba el compilador. Pero si la función solo va a manejar int, escríbela para int. La generalización prematura es mala señal en Go como en cualquier otro lenguaje, y el entrevistador espera que lo digas.
Interfaces y diseño de APIs
Las preguntas sobre interfaces evalúan el criterio de diseño, que es buena parte de lo que significa ser sénior. El proverbio de Go que debes conocer aquí es «acepta interfaces, devuelve structs». Una función debe aceptar la interfaz más pequeña que realmente use y devolver un tipo concreto, para que quien la llama conserve sus opciones.
example.gogo// Good: accepts the minimal behavior it needs. func Copy(dst io.Writer, src io.Reader) (int64, error) { return io.Copy(dst, src) }
A Copy le da igual si src es un archivo, una conexión de red o un buffer. Al aceptar io.Reader, funciona con todos ellos. Por eso io.Reader y io.Writer, con un único método cada una, son las interfaces más reutilizadas del lenguaje.
Hay algunos puntos más que vale la pena plantear. Mantén las interfaces pequeñas, idealmente de uno o dos métodos. Defínelas en el paquete que las consume y no en el que las implementa, para que los paquetes no dependan unos de otros sin motivo. Y abandona el hábito, común en otros lenguajes, de poner una interfaz delante de todo. Un struct con una sola implementación todavía no necesita una interfaz. Añádela cuando una segunda implementación o un doble de pruebas la necesite de verdad. Los entrevistadores notan las abstracciones construidas por si acaso, y ese es el tipo de código con el que lidia el 33 % al que le cuestan las convenciones idiomáticas (Encuesta de desarrolladores de Go 2025).
La ronda de live coding: implementa un rate limiter
La ronda en directo suele pedir algo pequeño pero concurrente. Los rate limiters salen a menudo porque combinan goroutines, channels, tiempo y limpieza de recursos. Un limitador de tipo token bucket basado en time.Ticker es lo bastante corto para escribirlo durante la entrevista y da mucho de qué hablar:
example.gogotype Limiter struct { tokens chan struct{} stop chan struct{} } func NewLimiter(perSecond int) *Limiter { l := &Limiter{ tokens: make(chan struct{}, perSecond), // burst capacity stop: make(chan struct{}), } ticker := time.NewTicker(time.Second / time.Duration(perSecond)) go func() { defer ticker.Stop() for { select { case <-ticker.C: select { case l.tokens <- struct{}{}: // refill one token default: // bucket full, drop the tick } case <-l.stop: return } } }() return l } // Allow reports whether a request may proceed right now. func (l *Limiter) Allow() bool { select { case <-l.tokens: return true default: return false } } func (l *Limiter) Close() { close(l.stop) }
El código importa menos que cómo hablas de él. Señala que el channel con buffer tokens te da capacidad de ráfaga sin coste adicional. Explica que el select interno con un default descarta los ticks de recarga cuando el bucket está lleno en lugar de bloquearse. Comenta que Close detiene la goroutine en segundo plano para que no se filtre, y que ticker.Stop() libera el temporizador. Después saca la alternativa. En producción lo normal sería usar golang.org/x/time/rate en lugar de escribir esto a mano, y saber cuándo elegir la solución estándar frente a una propia forma parte del criterio que buscan al contratar.
Preguntas frecuentes
¿Qué temas salen más en una entrevista sénior de Go?
Concurrencia, context, manejo de errores, interfaces y genéricos, más o menos en ese orden. El peso de cada tema sigue lo que construyen los desarrolladores de Go. El 75 % escribe servicios de API/RPC y el 62 % escribe herramientas CLI (Encuesta de desarrolladores de Go 2024, segundo semestre), y ambos tipos de trabajo son concurrentes y están llenos de formas de fallar. No hay ninguna estadística publicada y creíble que clasifique la frecuencia de preguntas concretas, así que desconfía de cualquier blog que diga tenerla.
¿Necesito saber genéricos para una entrevista de Go?
Deberías entenderlos, y importa más que sepas cuándo no usarlos. Los genéricos llegaron en Go 1.18 como el mayor cambio de la historia del lenguaje (blog de Go), así que pueden salir. Aun así, la mayor parte del Go idiomático sigue sin usarlos, y los candidatos sénior suman puntos cuando explican que los paquetes estándar slices y maps cubren la mayoría de las necesidades.
¿Qué importancia tiene el race detector?
La suficiente como para que los entrevistadores esperen que lo menciones sin que te lo pidan. El race detector de Uber encontró cerca de 2.000 carreras de datos en su monorepo de Go, y 210 ingenieros corrigieron unas 1.100 en seis meses (Uber Engineering). Decir «lo ejecutaría con go test -race» le indica al entrevistador que has trabajado con código de producción.
¿Con qué versión de Go debo prepararme?
Con la toolchain estable actual, que a principios de 2026 es Go 1.26 (notas de la versión Go 1.26). Describir comportamientos de versiones antiguas deja ver que no estás al día, por ejemplo afirmar que para recorrer un entero hace falta un bucle de tres cláusulas. La forma for range n funciona desde Go 1.22.
¿Sigue valiendo la pena especializarse en Go?
Sí. El 17,4 % de los desarrolladores profesionales afirmó haber trabajado a fondo con Go durante el último año (Encuesta de desarrolladores de Stack Overflow 2025). En la encuesta oficial, el 91 % de los participantes dijo estar satisfecho con el lenguaje y el 63 % dijo estar muy satisfecho (Encuesta de desarrolladores de Go 2025). La demanda de ingenieros sénior de Go acompaña esa adopción.
Empieza tu camino hacia el nivel sénior en Go
Leer sobre estos temas no significa que puedas escribirlos bajo presión. Las entrevistas sénior premian la memoria muscular: un worker pool que escribes sin pensar, una cadena de errores que envuelves por reflejo, un genérico que sabes dejar fuera. Eso se consigue escribiendo Go real todos los días con la toolchain actual, con feedback sobre si tu código es idiomático. Ese feedback es lo difícil de encontrar, y las convenciones idiomáticas son justo lo que más le cuesta al 33 % de la comunidad de Go.
LevelUpGo está pensado para eso. Cada lección es un ejercicio en el navegador que se ejecuta con la última versión estable de Go y corrige tu código automáticamente, así que practicas como te evaluará la entrevista en lugar de solo leer.
Aquí puedes practicar cada tema de este artículo:
- Concurrencia,
contexty channels: el curso Go Fundamentals construye goroutines, channels y cancelación desde los fundamentos. Puedes empezar gratis y no necesitas tarjeta de crédito. - Manejo de errores, interfaces y convenciones idiomáticas: la ruta Clean Go Code tiene ejercicios corregidos sobre
errors.Is/errors.As, interfaces pequeñas y el criterio de diseño que trata este artículo. - La hoja de ruta completa de LevelUpGo muestra cómo avanzan los cursos desde los fundamentos hasta el diseño de nivel sénior.
Empieza el curso gratis, escribe Go todos los días hasta la entrevista y llegarás capaz de explicar tu razonamiento en lugar de adivinarlo.
