Preguntas para Entrevistas de Desarrollador Backend (Go): Guía Completa

Milad Bonakdar
Autor
Domina el desarrollo backend en Go con preguntas esenciales para entrevistas que cubren concurrencia, interfaces, manejo de errores y diseño de sistemas. Preparación perfecta para entrevistas de desarrolladores Go.
Introducción
Go (Golang) se ha convertido en un lenguaje dominante para construir sistemas backend escalables, microservicios y aplicaciones nativas de la nube. Su simplicidad, su sólido modelo de concurrencia y su rendimiento lo convierten en una de las principales opciones para los equipos de ingeniería modernos.
Esta guía cubre las preguntas esenciales de la entrevista para los desarrolladores de backend que se especializan en Go. Exploramos los conceptos centrales del lenguaje, los patrones de concurrencia, el manejo de errores y el diseño del sistema para ayudarte a superar tu próxima entrevista.
Conceptos básicos de Go
1. ¿Qué diferencia a Go de otros lenguajes como Java o Python?
Respuesta: Go fue diseñado por Google para abordar los desafíos del desarrollo de software a gran escala. Las diferencias clave incluyen:
- Simplicidad: Go tiene un pequeño conjunto de palabras clave y carece de características complejas como la herencia o la sobrecarga de métodos, priorizando la legibilidad.
- Concurrencia: Soporte de primera clase para la concurrencia a través de Goroutines y Channels, lo que facilita la escritura de programas concurrentes escalables.
- Compilación: Go se compila directamente en código de máquina (binarios vinculados estáticamente), ofreciendo velocidades de inicio y ejecución rápidas sin una máquina virtual (JVM).
- Recolección de basura: Recolección de basura eficiente optimizada para baja latencia.
Frecuencia: Común Dificultad: Fácil
2. Explica la diferencia entre Arrays y Slices.
Respuesta:
- Arrays: Secuencias de tamaño fijo de elementos del mismo tipo. El tamaño es parte del tipo (por ejemplo,
[5]intes diferente de[10]int). Son tipos de valor; asignar un array a otro copia todos los elementos. - Slices: Vistas dinámicas y flexibles de un array subyacente. Consisten en un puntero al array, una longitud y una capacidad. Los slices son como referencias; pasar un slice a una función permite la modificación de los elementos subyacentes sin copiar todos los datos.
Frecuencia: Común Dificultad: Fácil
3. ¿Cómo funcionan las Interfaces en Go? ¿Qué es la implementación implícita?
Respuesta: Las interfaces en Go son colecciones de firmas de métodos.
- Implementación implícita: A diferencia de Java o C#, un tipo no declara explícitamente que implementa una interfaz (sin la palabra clave
implements). Si un tipo define todos los métodos declarados en una interfaz, implementa automáticamente esa interfaz. - Duck Typing: "Si camina como un pato y grazna como un pato, es un pato". Esto desacopla la definición de la implementación, haciendo que el código sea más flexible y más fácil de simular para las pruebas.
Frecuencia: Común Dificultad: Media
4. ¿Qué es la palabra clave defer y cómo funciona?
Respuesta:
defer programa una llamada a una función para que se ejecute inmediatamente antes de que la función regrese. Se utiliza comúnmente para la limpieza de recursos, como cerrar archivos, desbloquear mutexes o cerrar conexiones de bases de datos.
- Orden LIFO: Las llamadas diferidas se ejecutan en orden Último en entrar, Primero en salir (LIFO).
- Evaluación de argumentos: Los argumentos de las funciones diferidas se evalúan cuando se ejecuta la declaración
defer, no cuando se ejecuta la llamada.
Ejemplo:
Frecuencia: Común Dificultad: Fácil
Concurrencia
5. Explica las Goroutines y en qué se diferencian de los hilos del sistema operativo.
Respuesta:
- Goroutines: Hilos ligeros gestionados por el tiempo de ejecución de Go. Comienzan con una pequeña pila (por ejemplo, 2 KB) que crece y se reduce dinámicamente. Miles de goroutines pueden ejecutarse en un solo hilo del sistema operativo.
- Hilos del sistema operativo: Gestionados por el kernel, tienen pilas grandes fijas (por ejemplo, 1 MB) y el cambio de contexto es costoso.
- Programación M:N: El tiempo de ejecución de Go multiplexa M goroutines en N hilos del sistema operativo, manejando la programación de manera eficiente en el espacio del usuario.
Frecuencia: Muy común Dificultad: Media
6. ¿Qué son los Channels? ¿Con búfer vs. sin búfer?
Respuesta: Los channels son conductos tipados que permiten a las goroutines comunicarse y sincronizar la ejecución.
- Channels sin búfer: No tienen capacidad. Una operación de envío se bloquea hasta que un receptor esté listo, y viceversa. Proporcionan una fuerte sincronización.
- Channels con búfer: Tienen una capacidad. Una operación de envío solo se bloquea si el búfer está lleno. Una operación de recepción solo se bloquea si el búfer está vacío. Desacoplan el remitente y el receptor hasta cierto punto.
Frecuencia: Común Dificultad: Media
7. ¿Cómo manejas las condiciones de carrera en Go?
Respuesta: Una condición de carrera ocurre cuando varias goroutines acceden a la memoria compartida simultáneamente, y al menos un acceso es una escritura.
- Detección: Utiliza el detector de carreras incorporado:
go run -raceogo test -race. - Prevención:
- Channels: "No te comuniques compartiendo memoria; en su lugar, comparte memoria comunicándote".
- Paquete Sync: Utiliza
sync.Mutexosync.RWMutexpara bloquear secciones críticas. - Operaciones atómicas: Utiliza
sync/atomicpara contadores o banderas simples.
Frecuencia: Común Dificultad: Difícil
8. ¿Para qué se utiliza la declaración select?
Respuesta:
La declaración select permite a una goroutine esperar en múltiples operaciones de comunicación. Se bloquea hasta que uno de sus casos pueda ejecutarse, luego ejecuta ese caso. Si varios están listos, elige uno al azar.
- Tiempos de espera: Se pueden implementar utilizando
time.After. - Operaciones sin bloqueo: Un caso
defaulthace que el select no se bloquee si ningún otro caso está listo.
Ejemplo:
Frecuencia: Media Dificultad: Media
Manejo de errores y robustez
9. ¿Cómo funciona el manejo de errores en Go?
Respuesta:
Go trata los errores como valores. Las funciones devuelven un tipo error (generalmente como el último valor de retorno) en lugar de lanzar excepciones.
- Verificar errores: Los llamadores deben verificar explícitamente si el error es
nil. - Errores personalizados: Puedes crear tipos de error personalizados implementando la interfaz
error(que tiene un solo métodoError() string). - Envoltura: Go 1.13 introdujo la envoltura de errores (
fmt.Errorf("%w", err)) para agregar contexto mientras se conserva el error original para la inspección (usandoerrors.Isyerrors.As).
Frecuencia: Común Dificultad: Fácil
10. ¿Qué son Panic y Recover? ¿Cuándo deberías usarlos?
Respuesta:
- Panic: Detiene el flujo de control ordinario y comienza a entrar en pánico. Es similar a una excepción, pero debe reservarse para errores irrecuperables (por ejemplo, desreferencia de puntero nulo, índice fuera de los límites).
- Recover: Una función incorporada que recupera el control de una goroutine en pánico. Solo es útil dentro de una función
defer. - Uso: Generalmente desaconsejado para el flujo de control normal. Utiliza valores
errorpara las condiciones de error esperadas. Panic/recover se utiliza principalmente para situaciones verdaderamente excepcionales o dentro de bibliotecas/frameworks para evitar que un fallo derribe todo el servidor.
Frecuencia: Media Dificultad: Media
Diseño del sistema y Backend
11. ¿Cómo estructurarías una aplicación web en Go?
Respuesta: Si bien Go no impone una estructura, un estándar común es el "Diseño estándar del proyecto Go":
/cmd: Aplicaciones principales (puntos de entrada)./pkg: Código de biblioteca que está bien para ser utilizado por aplicaciones externas./internal: Código de aplicación y biblioteca privada (aplicado por el compilador de Go)./api: Especificaciones OpenAPI/Swagger, definiciones de protocolo./configs: Archivos de configuración.- Arquitectura Limpia: Separar las preocupaciones en capas (Entrega/Handler, Caso de uso/Servicio, Repositorio/Datos) para que la aplicación sea comprobable y mantenible.
Frecuencia: Común Dificultad: Media
12. ¿Cómo funciona el paquete context y por qué es importante?
Respuesta:
El paquete context es esencial para gestionar los valores con ámbito de solicitud, las señales de cancelación y los plazos en los límites de la API y las goroutines.
- Cancelación: Si un usuario cancela una solicitud, el contexto se cancela y todas las goroutines generadas que realizan el trabajo para esa solicitud deben detenerse para ahorrar recursos.
- Tiempos de espera:
context.WithTimeoutasegura que las consultas de la base de datos o las llamadas a la API externa no se cuelguen para siempre. - Valores: Puede llevar datos específicos de la solicitud como el ID de usuario o los tokens de autenticación (úsalos con moderación).
Frecuencia: Muy común Dificultad: Difícil
13. ¿Qué es la inyección de dependencias y cómo se hace en Go?
Respuesta: La inyección de dependencias (DI) es un patrón de diseño donde un objeto recibe otros objetos de los que depende.
- En Go: Generalmente se implementa pasando dependencias (como una conexión de base de datos o un registrador) al constructor o a la función de fábrica de una estructura, a menudo a través de interfaces.
- Beneficios: Hace que el código sea más modular y comprobable (fácil de intercambiar la base de datos real con una simulación).
- Frameworks: Si bien se prefiere la DI manual por simplicidad, existen bibliotecas como
google/wireouber-go/digpara gráficos complejos.
Frecuencia: Media Dificultad: Media
Base de datos y herramientas
14. ¿Cómo manejas JSON en Go?
Respuesta:
Go utiliza el paquete encoding/json.
- Etiquetas de estructura: Utiliza etiquetas como
`json:"nombre_campo"`para mapear los campos de la estructura a las claves JSON. - Marshal: Convierte una estructura Go en una cadena JSON (slice de bytes).
- Unmarshal: Analiza los datos JSON en una estructura Go.
- Streaming:
json.Decoderyjson.Encoderson mejores para cargas útiles grandes, ya que procesan flujos de datos.
Frecuencia: Común Dificultad: Fácil
15. ¿Cuáles son algunas herramientas comunes de Go que utilizas?
Respuesta:
go mod: Gestión de dependencias.go fmt: Formatea el código al estilo estándar.go vet: Examina el código en busca de construcciones sospechosas.go test: Ejecuta pruebas y puntos de referencia.pprof: Herramienta de creación de perfiles para analizar el uso de la CPU y la memoria.delve: Depurador para Go.
Frecuencia: Común Dificultad: Fácil
Temas avanzados y mejores prácticas
16. ¿Qué son los genéricos en Go y cuándo deberías usarlos?
Respuesta: Los genéricos (introducidos en Go 1.18) te permiten escribir funciones y estructuras de datos que funcionan con cualquiera de un conjunto de tipos, en lugar de un tipo específico.
- Parámetros de tipo: Definidos usando corchetes
[]. Por ejemplo,func Map[K comparable, V any](m map[K]V) ... - Restricciones: Interfaces que definen el conjunto de tipos permitidos (por ejemplo,
any,comparable). - Uso: Utilízalos para reducir la duplicación de código para algoritmos que se aplican a múltiples tipos (como la clasificación, el filtrado o las estructuras de datos como Sets/Trees). Evita el uso excesivo; si una interfaz es suficiente, utilízala.
Frecuencia: Común Dificultad: Media
17. Explica las pruebas basadas en tablas en Go.
Respuesta: Las pruebas basadas en tablas son un patrón preferido en Go donde los casos de prueba se definen como un slice de estructuras (la "tabla"). Cada estructura contiene los argumentos de entrada y la salida esperada.
- Beneficios:
- Separación limpia de la lógica de prueba y los datos de prueba.
- Fácil de agregar nuevos casos de prueba (solo agrega una fila a la tabla).
- Mensajes de error claros que muestran exactamente qué entrada falló.
- Ejemplo:
Frecuencia: Común Dificultad: Fácil
18. ¿Qué es el patrón Middleware en los servidores HTTP de Go?
Respuesta:
Middleware es una función que envuelve un http.Handler para realizar una lógica de pre o post procesamiento antes de pasar el control al siguiente handler.
- Firma:
func(next http.Handler) http.Handler - Casos de uso: Registro, autenticación, recuperación de pánico, limitación de velocidad, CORS.
- Encadenamiento: El middleware se puede encadenar (por ejemplo,
Log(Auth(Handler))).
Ejemplo:
Frecuencia: Muy común Dificultad: Media
19. ¿Cómo implementas el apagado elegante en un servidor Go?
Respuesta: El apagado elegante garantiza que un servidor deje de aceptar nuevas solicitudes, pero termina de procesar las solicitudes activas antes de salir.
- Mecanismo:
- Escucha las señales del sistema operativo (SIGINT, SIGTERM) usando
os/signal. - Crea un
context.WithTimeoutpara permitir una ventana de limpieza (por ejemplo, 5-10 segundos). - Llama a
server.Shutdown(ctx)en elhttp.Server. - Cierra las conexiones de la base de datos y otros recursos.
- Escucha las señales del sistema operativo (SIGINT, SIGTERM) usando
- Importancia: Evita la pérdida de datos y los errores del cliente durante las implementaciones.
Frecuencia: Común Dificultad: Difícil
20. ¿Cuándo deberías usar sync.Map en lugar de un mapa normal con un Mutex?
Respuesta:
sync.Map es una implementación de mapa segura para la concurrencia en la biblioteca estándar.
- Casos de uso:
- Contención de caché: Cuando la entrada para una clave dada solo se escribe una vez, pero se lee muchas veces (por ejemplo, cachés de carga diferida).
- Claves disjuntas: Cuando varias goroutines leen, escriben y sobrescriben entradas para conjuntos de claves disjuntos.
- Compensación: Para casos de uso general (actualizaciones frecuentes de lectura/escritura), un
mapnormal protegido por unsync.RWMutexsuele ser más rápido y tiene una mejor seguridad de tipo (ya quesync.Mapusaany).
Frecuencia: Poco común Dificultad: Difícil


