diciembre 21, 2025
12 min de lectura

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

interview
career-advice
job-search
Preguntas para Entrevistas de Desarrollador Backend (Go): Guía Completa
Milad Bonakdar

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]int es 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:

func readFile(filename string) {
    f, err := os.Open(filename)
    if err != nil {
        return
    }
    defer f.Close() // Asegura que el archivo se cierre cuando la función salga
    // ... procesar el archivo
}

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 -race o go test -race.
  • Prevención:
    • Channels: "No te comuniques compartiendo memoria; en su lugar, comparte memoria comunicándote".
    • Paquete Sync: Utiliza sync.Mutex o sync.RWMutex para bloquear secciones críticas.
    • Operaciones atómicas: Utiliza sync/atomic para 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 default hace que el select no se bloquee si ningún otro caso está listo.

Ejemplo:

select {
case msg := <-ch1:
    fmt.Println("Recibido", msg)
case ch2 <- "hola":
    fmt.Println("Enviado hola")
case <-time.After(1 * time.Second):
    fmt.Println("Tiempo de espera")
}

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étodo Error() 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 (usando errors.Is y errors.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 error para 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.WithTimeout asegura 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/wire o uber-go/dig para 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.Decoder y json.Encoder son 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:
func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {1, 1, 2},
        {2, -2, 0},
    }
    for _, tt := range tests {
        result := Add(tt.a, tt.b)
        if result != tt.expected {
            t.Errorf("Add(%d, %d): expected %d, got %d", tt.a, tt.b, tt.expected, result)
        }
    }
}

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:

func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println(r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

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:
    1. Escucha las señales del sistema operativo (SIGINT, SIGTERM) usando os/signal.
    2. Crea un context.WithTimeout para permitir una ventana de limpieza (por ejemplo, 5-10 segundos).
    3. Llama a server.Shutdown(ctx) en el http.Server.
    4. Cierra las conexiones de la base de datos y otros recursos.
  • 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:
    1. 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).
    2. 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 map normal protegido por un sync.RWMutex suele ser más rápido y tiene una mejor seguridad de tipo (ya que sync.Map usa any).

Frecuencia: Poco común Dificultad: Difícil

Newsletter subscription

Consejos de carrera semanales que realmente funcionan

Recibe las últimas ideas directamente en tu bandeja de entrada

Destácate ante los Reclutadores y Consigue el Trabajo de Tus Sueños

Únete a miles que transformaron sus carreras con currículums impulsados por IA que pasan el ATS e impresionan a los gerentes de contratación.

Comienza a crear ahora

Compartir esta publicación

Haz que tus 6 Segundos Cuenten

Los reclutadores escanean currículums durante un promedio de solo 6 a 7 segundos. Nuestras plantillas probadas están diseñadas para captar la atención al instante y mantenerlos leyendo.