Top 25 preguntas de entrevista Go: guía completa para desarrolladores

Domina las entrevistas de Go con las 25 preguntas más frecuentes. Goroutines, channels, interfaces y patrones de concurrencia con ejemplos de código.

Preguntas de entrevista Go - Guía completa de preparación

Las entrevistas técnicas de Go evalúan la comprensión de los conceptos fundamentales del lenguaje: concurrencia, gestión de memoria y patrones idiomáticos. Esta guía cubre las 25 preguntas más frecuentes con respuestas detalladas y ejemplos de código.

Consejo para la entrevista

Go valora la simplicidad y la legibilidad. Los entrevistadores buscan respuestas concisas que demuestren comprensión profunda en lugar de soluciones excesivamente complejas.

Fundamentos del lenguaje Go

1. ¿Cuál es la diferencia entre var y :=?

La declaración var permite especificar el tipo explícitamente y funciona a nivel de paquete. El operador := infiere el tipo de forma automática, pero solo se utiliza dentro de funciones.

declaration.gogo
package main

// Package level - var required
var globalConfig = "production"

func main() {
    // var with explicit type
    var count int = 10

    // var with type inference
    var name = "Alice"

    // Short declaration - functions only
    age := 25

    // Multiple declarations
    var (
        host = "localhost"
        port = 8080
    )
}

La declaración corta := se prefiere dentro de las funciones por concisión, mientras que var sigue siendo necesaria para variables a nivel de paquete.

2. ¿Cómo funciona el sistema de tipos de Go?

Go utiliza tipado estático con inferencia de tipos. El lenguaje distingue entre tipos por valor (copiados al asignarlos) y tipos por referencia (que comparten la estructura subyacente).

types.gogo
package main

import "fmt"

func main() {
    // Value types - full copy
    a := [3]int{1, 2, 3}
    b := a          // Copies the array
    b[0] = 100      // Doesn't modify a
    fmt.Println(a)  // [1 2 3]

    // Reference types - share data
    slice1 := []int{1, 2, 3}
    slice2 := slice1    // Same underlying array
    slice2[0] = 100     // Also modifies slice1
    fmt.Println(slice1) // [100 2 3]

    // Maps are also references
    m1 := map[string]int{"a": 1}
    m2 := m1
    m2["a"] = 100
    fmt.Println(m1["a"]) // 100
}

Los arrays son tipos por valor, mientras que los slices, los maps y los channels son tipos por referencia.

3. Explica la diferencia entre arrays y slices

Los arrays tienen un tamaño fijo definido en tiempo de compilación. Los slices son vistas dinámicas sobre un array subyacente, con tres componentes: puntero, longitud y capacidad.

arrays_slices.gogo
package main

import "fmt"

func main() {
    // Array - fixed size, value type
    arr := [5]int{1, 2, 3, 4, 5}

    // Slice - view over the array
    slice := arr[1:4]  // [2 3 4]
    fmt.Printf("len=%d, cap=%d\n", len(slice), cap(slice))
    // len=3, cap=4

    // Modifications affect original array
    slice[0] = 20
    fmt.Println(arr) // [1 20 3 4 5]

    // Direct creation with make
    dynamic := make([]int, 3, 10)
    // len=3, cap=10

    // Append may reallocate
    dynamic = append(dynamic, 1, 2, 3, 4, 5)
}

Los slices son el tipo preferido para colecciones dinámicas en Go.

4. ¿Cómo funciona la sentencia defer?

defer programa la ejecución de una llamada al final de la función que la contiene. Las llamadas diferidas se apilan y se ejecutan en orden LIFO (último en entrar, primero en salir).

defer.gogo
package main

import (
    "fmt"
    "os"
)

func main() {
    // LIFO order
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    // Prints: 3, 2, 1
}

// Typical use case: resource cleanup
func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close() // Always executes

    // Read file...
    return os.ReadFile(path)
}

// Caution: arguments are evaluated immediately
func deferArgs() {
    x := 10
    defer fmt.Println(x) // Captures 10
    x = 20
    // Prints: 10
}

defer garantiza la ejecución incluso durante un panic, lo que lo hace ideal para liberar recursos.

5. ¿Qué es una interfaz en Go?

Una interfaz define un conjunto de métodos. Cualquier tipo que implemente esos métodos satisface la interfaz de forma implícita, sin necesidad de una declaración explícita.

interfaces.gogo
package main

import "fmt"

// Interface definition
type Writer interface {
    Write([]byte) (int, error)
}

// Type that implicitly implements Writer
type FileLogger struct {
    path string
}

func (f *FileLogger) Write(data []byte) (int, error) {
    // Write to file
    fmt.Println("Writing to", f.path)
    return len(data), nil
}

// Empty interface - accepts any type
func printAny(v interface{}) {
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

// Type assertion
func process(w Writer) {
    // Type check
    if fl, ok := w.(*FileLogger); ok {
        fmt.Println("FileLogger with path:", fl.path)
    }
}

La implementación implícita de interfaces permite un fuerte desacoplamiento entre paquetes.

Concurrencia y goroutines

6. ¿Qué es una goroutine y en qué se diferencia de un thread?

Una goroutine es un hilo ligero gestionado por el runtime de Go. Usa unos pocos KB de stack (frente a varios MB de un hilo del sistema) y el scheduler de Go multiplexa miles de goroutines sobre unos pocos hilos del sistema.

goroutines.gogo
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup

    // Launch 1000 goroutines
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("Goroutine %d finished\n", id)
        }(i) // Pass i by value
    }

    wg.Wait()
    fmt.Println("All goroutines completed")
}
Trampa habitual

Conviene pasar siempre por valor las variables de bucle a las goroutines. De lo contrario, todas pueden capturar el mismo valor final.

7. Explica cómo funcionan los channels

Los channels permiten la comunicación y la sincronización entre goroutines. Pueden ser con búfer (con capacidad) o sin búfer (síncronos).

channels.gogo
package main

import "fmt"

func main() {
    // Unbuffered channel - blocks until received
    ch := make(chan int)

    go func() {
        ch <- 42 // Blocks until read
    }()

    value := <-ch // Receives value
    fmt.Println(value)

    // Buffered channel - doesn't block until full
    buffered := make(chan string, 2)
    buffered <- "first"
    buffered <- "second"
    // buffered <- "third" // Would block

    fmt.Println(<-buffered) // "first"
    fmt.Println(<-buffered) // "second"
}

Los channels sin búfer garantizan la sincronización, mientras que los channels con búfer permiten un desacoplamiento temporal.

8. ¿Cómo se usa select con varios channels?

select espera de forma simultánea sobre varias operaciones de channel. Se ejecuta la primera que esté lista, eligiendo aleatoriamente en caso de empate.

select.gogo
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    // Wait with timeout
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        case <-time.After(500 * time.Millisecond):
            fmt.Println("Timeout")
        }
    }

    // Non-blocking select with default
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("No message available")
    }
}

select es la herramienta fundamental para gestionar la concurrencia en Go con elegancia.

9. ¿Cómo se evitan las race conditions?

Las race conditions ocurren cuando varias goroutines acceden a datos compartidos sin sincronización. Go ofrece varios mecanismos de protección.

race_conditions.gogo
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

// Solution 1: Mutex
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

// Solution 2: RWMutex for read-heavy workloads
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // Multiple readers allowed
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // Single writer
    defer c.mu.Unlock()
    c.data[key] = value
}

// Solution 3: atomic for simple counters
var atomicCounter int64

func incrementAtomic() {
    atomic.AddInt64(&atomicCounter, 1)
}

func main() {
    // Detection: go run -race main.go
    counter := SafeCounter{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println("Count:", counter.count)
}

El flag -race del compilador detecta race conditions en tiempo de ejecución.

10. Explica el patrón worker pool

El patrón worker pool limita la concurrencia creando un número fijo de goroutines que procesan tareas desde una cola.

worker_pool.gogo
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(100 * time.Millisecond) // Simulate work
        results <- job * 2
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    var wg sync.WaitGroup

    // Start workers
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // Send jobs
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    // Wait and close results
    go func() {
        wg.Wait()
        close(results)
    }()

    // Collect results
    for result := range results {
        fmt.Println("Result:", result)
    }
}

Este patrón evita la sobrecarga de memoria y CPU asociada a crear demasiadas goroutines.

¿Listo para aprobar tus entrevistas de Go?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Manejo de errores y panic/recover

11. ¿Cómo se gestionan los errores en Go?

Go usa valores de retorno explícitos para los errores, sin excepciones. Por convención, error es el último parámetro retornado.

errors.gogo
package main

import (
    "errors"
    "fmt"
)

// Sentinel errors for comparison
var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("access unauthorized")
)

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation %s: %s", e.Field, e.Message)
}

func validateAge(age int) error {
    if age < 0 {
        return &ValidationError{
            Field:   "age",
            Message: "must be positive",
        }
    }
    return nil
}

func main() {
    // Basic check
    if err := validateAge(-5); err != nil {
        // Type assertion for custom error
        var valErr *ValidationError
        if errors.As(err, &valErr) {
            fmt.Printf("Field: %s\n", valErr.Field)
        }
    }

    // Sentinel error comparison
    err := findUser("unknown")
    if errors.Is(err, ErrNotFound) {
        fmt.Println("User not found")
    }
}

func findUser(id string) error {
    // Error wrapping with context
    return fmt.Errorf("findUser %s: %w", id, ErrNotFound)
}

Envolver con %w encadena los errores manteniendo la posibilidad de comprobar el error original.

12. ¿Cuándo conviene usar panic y recover?

panic interrumpe el flujo normal y desenrolla la pila. recover captura el panic en un defer y permite que la ejecución continúe.

panic_recover.gogo
package main

import "fmt"

func safeOperation() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()

    riskyOperation()
    return nil
}

func riskyOperation() {
    // Simulates an operation that can panic
    panic("something went wrong")
}

// Legitimate use case: initialization validation
func MustCompileRegex(pattern string) *Regexp {
    r, err := regexp.Compile(pattern)
    if err != nil {
        panic(err) // Programming error
    }
    return r
}

func main() {
    err := safeOperation()
    if err != nil {
        fmt.Println("Recovered error:", err)
    }
    fmt.Println("Program continues")
}
Regla de oro

Usa panic solo para errores de programación (invariantes violadas). Para errores esperados (archivo ausente, fallos de red), conviene siempre devolver un error.

Structs, métodos y embedding

13. ¿Qué diferencia hay entre receivers por valor y por puntero?

Un receiver por valor recibe una copia del struct, mientras que un receiver por puntero recibe una referencia y puede modificar el original.

receivers.gogo
package main

import "fmt"

type Counter struct {
    value int
}

// Value receiver - works on copy
func (c Counter) GetValue() int {
    return c.value
}

// Pointer receiver - modifies original
func (c *Counter) Increment() {
    c.value++
}

// Pointer receiver for large structs (avoids copy)
type LargeStruct struct {
    data [1000]int
}

func (l *LargeStruct) Process() {
    // Avoids copying 8000 bytes
}

func main() {
    c := Counter{value: 0}
    c.Increment() // Go automatically converts
    fmt.Println(c.GetValue()) // 1

    // Careful with interfaces
    var _ fmt.Stringer = &c // OK if method on *Counter
}

Regla: si un método usa receiver por puntero, todos los métodos del tipo deberían usar receiver por puntero por coherencia.

14. ¿Cómo funciona el embedding en Go?

El embedding incluye un tipo dentro de otro, heredando sus métodos y campos. No es herencia clásica, sino composición.

embedding.gogo
package main

import "fmt"

type Logger struct {
    prefix string
}

func (l *Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.prefix, msg)
}

// Embedding Logger
type Service struct {
    *Logger // Pointer embedding
    name    string
}

func NewService(name string) *Service {
    return &Service{
        Logger: &Logger{prefix: name},
        name:   name,
    }
}

func main() {
    svc := NewService("API")

    // Promoted method - direct access
    svc.Log("Starting")

    // Explicit access also works
    svc.Logger.Log("Explicit")

    // Promoted field
    fmt.Println(svc.prefix) // "API"
}

El embedding permite composiciones flexibles evitando la rigidez de la herencia.

15. ¿Cómo se implementa el patrón singleton en Go?

El paquete sync ofrece sync.Once para garantizar una única ejecución de la inicialización, incluso con goroutines concurrentes.

singleton.gogo
package main

import (
    "fmt"
    "sync"
)

type Database struct {
    connectionString string
}

var (
    instance *Database
    once     sync.Once
)

func GetDatabase() *Database {
    once.Do(func() {
        fmt.Println("Single initialization")
        instance = &Database{
            connectionString: "postgres://...",
        }
    })
    return instance
}

func main() {
    // Concurrent calls - single initialization
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            db := GetDatabase()
            fmt.Printf("Instance: %p\n", db)
        }()
    }
    wg.Wait()
}

sync.Once es seguro para concurrencia y más elegante que un mutex con double-check locking.

Context y cancelación

16. ¿Para qué sirve el paquete context?

El paquete context gestiona deadlines, señales de cancelación y valores asociados a una request a lo largo del árbol de llamadas.

context.gogo
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // Context with timeout
    ctx, cancel := context.WithTimeout(
        context.Background(),
        2*time.Second,
    )
    defer cancel() // Always call cancel

    result := make(chan string, 1)

    go func() {
        // Simulate long operation
        time.Sleep(3 * time.Second)
        result <- "completed"
    }()

    select {
    case res := <-result:
        fmt.Println(res)
    case <-ctx.Done():
        fmt.Println("Timeout:", ctx.Err())
    }
}

// Propagation through functions
func fetchData(ctx context.Context, url string) ([]byte, error) {
    // Early check
    if ctx.Err() != nil {
        return nil, ctx.Err()
    }

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    // HTTP client respects context
    resp, err := http.DefaultClient.Do(req)
    // ...
}

Toda función potencialmente larga debería aceptar un context.Context como primer parámetro.

17. ¿Cómo se gestiona un graceful shutdown del programa?

Las señales del sistema, como SIGINT y SIGTERM, pueden capturarse para permitir un cierre limpio.

graceful_shutdown.gogo
package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    // Context cancelled on signal
    ctx, stop := signal.NotifyContext(
        context.Background(),
        syscall.SIGINT,
        syscall.SIGTERM,
    )
    defer stop()

    // Start server
    server := startServer()

    // Wait for signal
    <-ctx.Done()
    fmt.Println("\nShutting down...")

    // Timeout for graceful shutdown
    shutdownCtx, cancel := context.WithTimeout(
        context.Background(),
        5*time.Second,
    )
    defer cancel()

    if err := server.Shutdown(shutdownCtx); err != nil {
        fmt.Println("Shutdown error:", err)
    }

    fmt.Println("Shutdown complete")
}

Este patrón asegura que las conexiones activas terminen correctamente antes del cierre.

Tests y benchmarks

18. ¿Cómo se escriben tests en Go?

El paquete testing integrado proporciona la funcionalidad básica. Los tests se ubican en archivos *_test.go.

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

// Table-driven tests
func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -1, -2},
        {"mixed", -1, 5, 4},
        {"zero", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

Los table-driven tests son el patrón idiomático en Go para probar varios casos.

19. ¿Cómo se escriben benchmarks?

Los benchmarks usan testing.B y se ejecutan con go test -bench.

benchmark_test.gogo
package main

import (
    "strings"
    "testing"
)

func BenchmarkStringConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var s string
        for j := 0; j < 100; j++ {
            s += "a"
        }
    }
}

func BenchmarkStringBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var sb strings.Builder
        for j := 0; j < 100; j++ {
            sb.WriteString("a")
        }
        _ = sb.String()
    }
}

// Typical results:
// BenchmarkStringConcat-8      50000    28000 ns/op
// BenchmarkStringBuilder-8   1000000     1200 ns/op

Los benchmarks revelan diferencias de rendimiento entre implementaciones.

Generics (Go 1.18+)

20. ¿Cómo se usan los generics en Go?

Go 1.18 introdujo los parámetros de tipo, lo que permite escribir código genérico manteniendo la seguridad de tipos.

generics.gogo
package main

import "fmt"

// Generic function
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// Custom type constraint
type Number interface {
    int | int64 | float64
}

func Sum[T Number](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

// Generic type
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func main() {
    // Usage
    doubled := Map([]int{1, 2, 3}, func(n int) int {
        return n * 2
    })
    fmt.Println(doubled) // [2 4 6]

    fmt.Println(Sum([]int{1, 2, 3, 4, 5})) // 15

    stack := &Stack[string]{}
    stack.Push("hello")
    stack.Push("world")
    val, _ := stack.Pop()
    fmt.Println(val) // "world"
}

Los generics eliminan la necesidad de duplicar código o de usar interface{}.

Módulos y dependencias

21. ¿Cómo funciona el sistema de módulos de Go?

Los módulos de Go gestionan las dependencias con versionado semántico. El archivo go.mod define el módulo y sus dependencias.

go.mod examplego
module github.com/user/myproject

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/lib/pq v1.10.9
)

// Essential commands:
// go mod init github.com/user/project
// go mod tidy        - clean dependencies
// go get package@v1.2.3 - add/update
// go mod vendor      - copy locally
bash
# Updating dependencies
go get -u ./...           # All dependencies
go get -u=patch ./...     # Patches only

El archivo go.sum contiene los checksums criptográficos para garantizar la integridad de las dependencias.

22. ¿Cómo conviene estructurar un proyecto en Go?

La estructura estándar sigue convenciones de la comunidad sin imponer reglas estrictas.

text
myproject/
├── cmd/
│   └── api/
│       └── main.go      # Entry point
├── internal/            # Private to module
│   ├── handler/
│   ├── service/
│   └── repository/
├── pkg/                 # Reusable external code
├── go.mod
├── go.sum
└── README.md

La carpeta internal es especial: su contenido no puede ser importado por otros módulos.

Preguntas avanzadas

23. ¿Cómo funciona el garbage collector en Go?

Go utiliza un garbage collector concurrente, tricolor mark-and-sweep, optimizado para baja latencia.

gc_optimization.gogo
package main

import "runtime"

func main() {
    // GC configuration
    // GOGC=100 (default) - triggers GC when heap doubles

    // Force GC
    runtime.GC()

    // Memory statistics
    var stats runtime.MemStats
    runtime.ReadMemStats(&stats)

    println("Alloc:", stats.Alloc)
    println("NumGC:", stats.NumGC)
    println("PauseTotalNs:", stats.PauseTotalNs)
}

// Optimization techniques
// 1. Reuse allocations with sync.Pool
// 2. Pre-allocate slices with make([]T, 0, cap)
// 3. Avoid repeated string/[]byte conversions
// 4. Use pointers for large structs

La variable de entorno GODEBUG=gctrace=1 muestra los trazos del GC.

24. Explica el scheduler de Go

El scheduler de Go usa un modelo M:N que mapea N goroutines sobre M hilos del sistema, con tres entidades: G (goroutine), M (thread) y P (procesador lógico).

scheduler.gogo
package main

import (
    "fmt"
    "runtime"
)

func main() {
    // Number of logical processors (P)
    fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))

    // Number of active goroutines
    fmt.Println("NumGoroutine:", runtime.NumGoroutine())

    // Yield processor to other goroutines
    runtime.Gosched()

    // M:P:G model
    // - G: goroutine (lightweight stack ~2KB)
    // - M: OS thread (machine)
    // - P: logical processor (execution context)
    //
    // Each P has a local queue of Gs
    // Work stealing when queue is empty
}

El scheduler es preemptivo desde Go 1.14, lo que evita que una goroutine monopolice un P.

25. ¿Cómo se optimiza el rendimiento en Go?

La optimización empieza por hacer profiling para identificar cuellos de botella.

profiling.gogo
package main

import (
    "os"
    "runtime/pprof"
)

func main() {
    // CPU profiling
    f, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // Code to profile...

    // Memory profiling
    mf, _ := os.Create("mem.prof")
    defer mf.Close()
    pprof.WriteHeapProfile(mf)
}

// Analysis: go tool pprof cpu.prof

// Common optimization techniques:
// 1. Avoid allocations in hot loops
// 2. Use sync.Pool for reusable objects
// 3. Prefer []byte over string for mutations
// 4. Use bufio for I/O
// 5. Batch database operations
Regla de optimización

Conviene medir antes de optimizar. El profiling suele revelar sorpresas sobre los cuellos de botella reales.

Conclusión

Estas 25 preguntas cubren los conceptos fundamentales evaluados en las entrevistas de Go:

Checklist de preparación:

  • ✅ Dominio de las goroutines y los channels
  • ✅ Comprensión de las interfaces implícitas
  • ✅ Manejo idiomático de errores
  • ✅ Uso adecuado de context
  • ✅ Patrones de concurrencia (mutex, worker pool)
  • ✅ Tests y benchmarks
  • ✅ Conocimiento de los generics de Go 1.18+

La clave del éxito en una entrevista de Go: demostrar comprensión de los compromisos entre simplicidad y rendimiento, y saber cuándo aplicar cada patrón de concurrencia.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Etiquetas

#go
#golang
#interview
#concurrency
#goroutines

Compartir

Artículos relacionados