Preparación Junior Go

Preguntas de entrevista Junior para desarrolladores backend Go

15 preguntas seleccionadas para desarrolladores Junior Go que necesitan explicar fundamentos y práctica backend con claridad.

Empezar una entrevista IA Junior GoNo se requiere tarjeta. 1 sesión gratuita disponible.
Práctica de entrevista técnica en inglésDiseñado para hablantes no nativos que desean practicar entrevistas técnicas en inglés.

Sistema de tipos

1Explica cómo funcionan los valores cero (zero values) de Go en los tipos integrados y en los tipos similares a referencias, y por qué importan al declarar variables sin inicialización explícita.

En Go, una variable declarada sin un inicializador explícito se inicializa automáticamente al valor cero de su tipo. Los tipos numéricos pasan a ser 0, bool pasa a ser false, string pasa a ser "", y los arrays o structs se ponen a cero elemento a elemento o campo a campo. Los tipos similares a punteros o a referencias, como punteros, slices, maps, channels, funciones e interfaces, tienen nil como valor cero. Esto importa porque las variables de Go y los campos de struct omitidos arrancan en un estado determinista en lugar de contener basura, y muchas APIs están diseñadas para que el valor cero sea un valor por defecto útil, aunque algunos valores nil siguen necesitando inicialización antes de ciertas operaciones.

Probar responder esta pregunta con un coach de IA

2¿Cómo maneja Go la igualdad de structs y qué ocurre cuando un struct contiene campos incomparables?

Los valores struct en Go se pueden comparar con `==` y `!=` solo cuando cada campo del struct es comparable. La igualdad compara los campos correspondientes usando la propia regla de igualdad de cada campo. Si un struct contiene un campo incomparable como un slice, un map o una función, el tipo struct no es comparable, y comparar dos valores de ese tipo struct con `==` es un error en tiempo de compilación. Para tales structs, usa lógica de comparación personalizada o un ayudante adecuado de igualdad profunda, especialmente en pruebas.

Probar responder esta pregunta con un coach de IA

3Explica la inmutabilidad de string en Go y la relación entre string, []byte, bytes.Buffer y strings.Builder.

Un `string` en Go es una secuencia inmutable de bytes, a menudo texto UTF-8 pero no necesariamente UTF-8 válido. No se puede modificar un string in situ; para cambiar el contenido normalmente se convierte a `[]byte` para ediciones a nivel de byte o a `[]rune` para ediciones a nivel de code point, y luego se vuelve a convertir. Las conversiones normales entre `string` y `[]byte` copian datos y pueden asignar memoria, así que las conversiones repetidas o la concatenación repetida en bucles pueden ser costosas. `strings.Builder` está optimizado para construir strings de forma eficiente, mientras que `bytes.Buffer` es un buffer de bytes mutable útil para datos orientados a bytes e I/O y también puede producir un string.

Probar responder esta pregunta con un coach de IA

4¿Cómo funcionan los punteros en Go y qué operaciones se prohíben intencionadamente en comparación con C?

Un puntero en Go es un valor tipado que guarda la dirección de otro valor, por ejemplo *int apunta a un int. Se usa &x para obtener la dirección de un valor y *p para desreferenciar un puntero y leer o escribir el valor apuntado. Pasar o almacenar un puntero permite que varios sitios observen o muten el mismo valor subyacente, y un puntero puede ser nil. A diferencia de C, el Go seguro prohíbe intencionadamente la aritmética de punteros y la manipulación arbitraria de direcciones en bruto; las excepciones de bajo nivel requieren el paquete unsafe.

Probar responder esta pregunta con un coach de IA

5Explica cómo funcionan las constantes e iota en Go, incluyendo constantes typed frente a untyped.

Las constantes de Go son valores en tiempo de compilación: boolean, string o numéricos. Una constante typed tiene un tipo concreto. Una constante untyped no tiene un tipo concreto fijo hasta que se usa en un contexto tipado, y las constantes numéricas untyped se representan de forma exacta/con alta precisión hasta entonces; el valor debe ser representable en el tipo elegido. `iota` es un identificador predeclarado usado en declaraciones `const`: empieza en 0 en cada bloque const e incrementa en cada especificación de constante, lo que lo hace útil para constantes de estilo enum y bit flags.

Probar responder esta pregunta con un coach de IA

6¿Cómo funcionan las conversiones explícitas en Go y por qué el lenguaje evita muchas conversiones implícitas?

Go suele exigir conversiones explícitas con `T(x)` al cambiar el tipo de un valor, como `int64(i)` o `MyID(n)`. Esto mantiene visibles los cambios de tipo y evita conversiones implícitas sorprendentes de numéricos, boolean o string. Las conversiones solo se permiten según las reglas de conversión de Go y pueden cambiar el valor, por ejemplo por overflow/truncamiento de enteros, truncamiento de float a int hacia cero o pérdida de precisión. Las constantes untyped son más flexibles: pueden usarse en un contexto tipado si el valor de la constante es representable en ese tipo.

Probar responder esta pregunta con un coach de IA

Estructuras de datos

7Describe la diferencia entre arrays y slices en Go, incluyendo cómo se comportan la longitud, la capacidad y el almacenamiento subyacente.

Un array en Go tiene una longitud fija que forma parte de su tipo, como [3]int; almacena sus elementos directamente, y asignar o pasar un array copia todo el valor del array. Un slice, como []int, es un pequeño descriptor sobre un array subyacente: conceptualmente contiene un puntero a los elementos, una longitud y una capacidad. La longitud de un slice es el número de elementos visibles; su capacidad es cuántos elementos se pueden usar desde el inicio del slice antes de llegar al final del array de respaldo. Los slices son flexibles: rebanar (reslicing) cambia el descriptor, y append puede reutilizar el mismo array subyacente si la capacidad lo permite o asignar uno nuevo si no.

Probar responder esta pregunta con un coach de IA

8¿Cómo se comporta el tipo map de Go respecto a los tipos de clave, las claves ausentes, los maps nil y el orden de iteración?

Los tipos de clave de un map en Go deben ser comparables; slices, maps y funciones no se pueden usar directamente como claves. Buscar una clave que no está presente devuelve el valor cero del tipo del elemento, por lo que se usa la forma comma-ok (`v, ok := m[k]`) para distinguir la ausencia de un valor cero presente. Un map nil se puede leer y recorrer con range, pero asignar en él provoca panic; hay que inicializarlo antes de escribir. El orden de iteración de un map no está especificado y el código no debe depender de él.

Probar responder esta pregunta con un coach de IA

Gestión de memoria

9¿Cuándo conviene pasar un struct u otro valor por valor frente a por puntero en Go?

Pasa por valor cuando el valor es pequeño, de solo lectura para la llamada, o quieres semántica de copia independiente. Pasa por puntero cuando la función debe mutar el valor del llamador, copiar sería costoso, el tipo no debería copiarse, o nil/identidad compartida forma parte de la API. No asumas que los punteros siempre son más rápidos: pueden añadir aliasing, escapes al heap, trabajo de GC y un acceso menos amigable con la caché. Prioriza una semántica clara y mide con benchmarks las decisiones sensibles al rendimiento.

Probar responder esta pregunta con un coach de IA

Semántica del lenguaje

10¿Cómo se comportan la asignación y la copia para structs, arrays, maps y slices en Go?

La asignación en Go copia el valor que se asigna, pero el efecto depende del tipo. La asignación de un struct copia los campos del struct, y la de un array copia cada elemento. La asignación de un slice copia el header del slice—puntero, longitud y capacidad—de modo que los slices suelen compartir el mismo array de respaldo. La asignación de un map copia un descriptor del map de comportamiento similar a una referencia, de modo que ambas variables se refieren a los mismos datos del map. Para una propiedad independiente, haz una copia defensiva, por ejemplo usando copy o slices.Clone para slices y creando un map nuevo y copiando las entradas para maps.

Probar responder esta pregunta con un coach de IA

11¿Cómo capturan variables los closures en Go y qué errores pueden surgir en torno a bucles y goroutines?

Un closure en Go es un valor de función que hace referencia a variables de su ámbito léxico circundante. Captura las variables en sí, de modo que las mutaciones pueden ser observadas por el closure, y las variables capturadas pueden vivir más que la función que las creó si el closure escapa. Los errores en torno a bucles y goroutines ocurren cuando los closures comparten u observan una variable después de que ha cambiado, lo que a menudo hace que goroutines o callbacks diferidos vean un valor no deseado o provoquen data races sobre estado compartido. La solución habitual es pasar el valor pretendido como argumento al closure o crear una nueva copia local por iteración, y sincronizar el acceso concurrente cuando haga falta. Desde Go 1.22, las variables de bucle declaradas por `for`/`range` son por iteración, lo que elimina muchos bugs clásicos de captura de variables de bucle, pero las variables reutilizadas fuera del bucle y otro estado mutable compartido aún pueden causar problemas.

Probar responder esta pregunta con un coach de IA

12Describe la semántica de range sobre arrays, slices, maps, strings y channels.

`range` itera según el tipo del operando. Sobre un array o slice produce un índice y un valor de elemento; el valor del elemento es una copia, así que asignarle no modifica la colección. Hacer range sobre un valor de array copia el array para la iteración, mientras que hacer range sobre un slice usa el header del slice e indexa el array subyacente. Sobre un map produce clave y valor en un orden no especificado, y el valor es una copia. Sobre un string produce el índice de byte y el code point Unicode decodificado (`rune`), no un índice por posición de rune. Sobre un channel recibe valores hasta que el channel se cierra y se drena; hacer range sobre un channel nil se bloquea para siempre.

Probar responder esta pregunta con un coach de IA

Métodos

13¿Cuál es la diferencia entre receivers por valor y receivers por puntero, y cómo afectan las elecciones de receiver a la mutabilidad, la copia y la satisfacción de interfaces?

Un método con receiver por valor recibe una copia del receiver, por lo que es adecuado para comportamiento de solo lectura y tipos pequeños de estilo inmutable; los cambios en la copia del receiver no son visibles para el llamador. Un método con receiver por puntero recibe una copia de un puntero al receiver, por lo que puede mutar el original y evitar copiar valores grandes o que no deberían copiarse. La elección del receiver afecta a los method sets: los métodos con receivers por valor están en el method set tanto de T como de *T, mientras que los métodos con receivers por puntero solo están en el method set de *T. Por tanto, una interface que exige un método con receiver por puntero la satisface *T, no T, aunque un valor T direccionable pueda llamar al método con la sintaxis ordinaria de llamada a método.

Probar responder esta pregunta con un coach de IA

Interfaces

14Describe cómo funcionan las interfaces en Go y qué significa que un tipo satisfaga una interface de forma implícita.

Una interface en Go define un conjunto de métodos requeridos. Un tipo concreto satisface una interface de forma implícita cuando su method set contiene esos métodos; no hay declaración implements. Una variable de interface puede contener un valor dinámico concreto cuyo tipo satisface la interface, y las llamadas a métodos de la interface se despachan a la implementación de ese valor dinámico. La interface vacía, escrita interface{} o habitualmente any, no tiene métodos requeridos, así que todo tipo la satisface. Que T, *T o ambos satisfagan una interface depende de sus method sets y de las elecciones de receiver.

Probar responder esta pregunta con un coach de IA

15¿Cómo funcionan las type assertions y los type switches con valores de interface?

Una type assertion comprueba el valor dinámico almacenado en una interface: `v := x.(T)` tiene éxito si el tipo dinámico del valor de interface es `T`, o si `T` es una interface implementada por el valor dinámico. La forma de un solo resultado hace panic si falla. La forma comma-ok, `v, ok := x.(T)`, informa del éxito sin hacer panic. Un type switch, `switch v := x.(type)`, ramifica según el tipo dinámico del valor de interface, y `v` queda tipado según el case coincidente.

Probar responder esta pregunta con un coach de IA