Go: Fundamentos para Desarrolladores Java/Python en 2026
Aprende Go rápidamente aprovechando tu experiencia en Java o Python. Goroutines, channels, interfaces y patrones esenciales para una transición fluida.

Go (o Golang) se ha consolidado como el lenguaje de referencia para microservicios, herramientas CLI y sistemas distribuidos. Creado por Google en 2009, combina la simplicidad de Python con el rendimiento de C. Para desarrolladores que vienen de Java o Python, la transición a Go resulta sorprendentemente fluida una vez que los conceptos clave encajan.
Go impulsa Docker, Kubernetes, Terraform e innumerables infraestructuras cloud. Su compilación rápida, soporte nativo de concurrencia y despliegue en un solo binario lo hacen ideal para el desarrollo backend moderno.
Instalación y configuración de Go
Instalar Go es sencillo y consistente en todas las plataformas. La herramienta go gestiona compilación, dependencias y testing.
# install.sh
# Installation on macOS with Homebrew
brew install go
# Installation on Linux (Ubuntu/Debian)
sudo apt update && sudo apt install golang-go
# Verify installation
go version
# go version go1.22.0 linux/amd64La estructura de un proyecto Go sigue convenciones estrictas pero simples. El archivo go.mod define el módulo y sus dependencias.
# project-setup.sh
# Create a new project
mkdir my-project && cd my-project
go mod init github.com/user/my-project
# Generated structure:
# my-project/
# ├── go.mod # Module manifest
# └── main.go # Entry point
# Essential commands
go build # Compile the project
go run main.go # Compile and execute
go test ./... # Run all tests
go fmt ./... # Format code automaticallyPrimer programa en Go
A continuación, un programa simple que ilustra la sintaxis básica de Go. Comparar con Java y Python ayuda a visualizar las diferencias.
package main
import "fmt"
// Program entry point
func main() {
// Declaration with type inference
message := "Hello, Go!"
fmt.Println(message)
// Explicit declaration
var count int = 42
fmt.Printf("Count: %d\n", count)
}Lo que destaca inmediatamente: sin punto y coma, sin paréntesis alrededor de las condiciones e inferencia de tipos con :=. Go prioriza la concisión sin sacrificar la legibilidad.
Variables y tipos fundamentales
Go tiene tipado estático pero ofrece excelente inferencia de tipos. Los tipos básicos cubren la mayoría de los casos de uso.
package main
import "fmt"
func main() {
// Short declaration (inside functions only)
name := "Alice" // string
age := 30 // int
height := 1.75 // float64
active := true // bool
// Explicit declaration
var score int = 100
var rate float64 = 3.14
// Multiple declaration
var (
firstName string = "Bob"
lastName string = "Smith"
points int = 0
)
// Zero values (default values)
var count int // 0
var text string // "" (empty string)
var flag bool // false
var ptr *int // nil
fmt.Println(name, age, height, active)
}A diferencia de Java o Python, Go inicializa automáticamente las variables a su "valor cero": 0 para números, "" para strings, false para bools, nil para punteros y slices.
Funciones y retornos múltiples
Go permite retornar múltiples valores, una característica utilizada extensivamente para el manejo de errores.
package main
import (
"errors"
"fmt"
)
// Simple function with typed parameters
func add(a, b int) int {
return a + b
}
// Multiple returns (idiomatic pattern for errors)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Named returns
func getUser(id int) (name string, age int, err error) {
if id <= 0 {
err = errors.New("invalid ID")
return
}
name = "Alice"
age = 30
return
}
// Variadic function
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
func main() {
// Simple call
result := add(5, 3)
fmt.Println("5 + 3 =", result)
// Explicit error handling
quotient, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("10 / 3 = %.2f\n", quotient)
// Ignore a returned value with _
name, _, _ := getUser(1)
fmt.Println("User:", name)
// Variadic call
total := sum(1, 2, 3, 4, 5)
fmt.Println("Sum:", total)
}Structs y métodos
Los structs son los bloques de construcción para tipos personalizados en Go. Los métodos se asocian a los tipos mediante receptores.
package main
import "fmt"
// Struct definition
type User struct {
ID int
Username string
Email string
Active bool
}
// Constructor (convention: NewTypeName)
func NewUser(id int, username, email string) *User {
return &User{
ID: id,
Username: username,
Email: email,
Active: true,
}
}
// Method with value receiver (copy)
func (u User) FullInfo() string {
status := "inactive"
if u.Active {
status = "active"
}
return fmt.Sprintf("%s <%s> (%s)", u.Username, u.Email, status)
}
// Method with pointer receiver (modification possible)
func (u *User) Deactivate() {
u.Active = false
}
// Method with pointer receiver for modification
func (u *User) UpdateEmail(newEmail string) {
u.Email = newEmail
}
func main() {
// Create with constructor
user := NewUser(1, "alice", "alice@example.com")
fmt.Println(user.FullInfo())
// Modify via method
user.Deactivate()
fmt.Println(user.FullInfo())
// Direct creation
user2 := User{
ID: 2,
Username: "bob",
Email: "bob@example.com",
}
fmt.Println(user2.FullInfo())
}Se utiliza un receptor de puntero (*User) cuando el método modifica el estado o cuando el struct es grande. Se usa un receptor de valor (User) para métodos de solo lectura en structs ligeros.
Interfaces: polimorfismo implícito
Las interfaces en Go se implementan de forma implícita. Un tipo satisface una interfaz si implementa todos sus métodos, sin declaración explícita.
package main
import (
"fmt"
"math"
)
// Interface definition
type Shape interface {
Area() float64
Perimeter() float64
}
// Rectangle implements Shape implicitly
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Circle also implements Shape
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Function accepting the interface
func PrintShapeInfo(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
circle := Circle{Radius: 7}
// Polymorphism via interface
PrintShapeInfo(rect)
PrintShapeInfo(circle)
// Slice of interfaces
shapes := []Shape{rect, circle}
for _, shape := range shapes {
PrintShapeInfo(shape)
}
}Este enfoque difiere radicalmente de Java, donde implements es obligatorio. En Go, la conformidad es estructural, no nominal.
¿Listo para aprobar tus entrevistas de Go?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Slices y maps: colecciones dinámicas
Los slices son vistas dinámicas sobre arrays y los maps son tablas hash nativas.
package main
import "fmt"
func main() {
// Slice: dynamic array
numbers := []int{1, 2, 3, 4, 5}
// Append elements
numbers = append(numbers, 6, 7)
// Slicing (similar to Python)
subset := numbers[1:4] // [2, 3, 4]
fmt.Println("Subset:", subset)
// Create slice with make
scores := make([]int, 0, 10) // len=0, cap=10
scores = append(scores, 100, 95, 88)
// Iteration with range
for index, value := range numbers {
fmt.Printf("numbers[%d] = %d\n", index, value)
}
// Map: hash table
users := map[string]int{
"alice": 30,
"bob": 25,
}
// Add/Update
users["charlie"] = 35
// Check existence
age, exists := users["alice"]
if exists {
fmt.Println("Alice's age:", age)
}
// Delete
delete(users, "bob")
// Map iteration
for name, age := range users {
fmt.Printf("%s is %d years old\n", name, age)
}
}Manejo idiomático de errores
Go no tiene excepciones. Los errores son valores retornados explícitamente, lo que obliga a un manejo riguroso.
package main
import (
"errors"
"fmt"
"os"
)
// Sentinel error (for comparison)
var ErrNotFound = errors.New("resource not found")
var ErrInvalidInput = errors.New("invalid input")
// Custom error with context
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Function returning different error types
func GetUser(id int) (string, error) {
if id <= 0 {
return "", &ValidationError{
Field: "id",
Message: "must be positive",
}
}
if id > 1000 {
return "", ErrNotFound
}
return "Alice", nil
}
// Error wrapping (Go 1.13+)
func ReadConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config %s: %w", path, err)
}
return data, nil
}
func main() {
// Basic pattern
user, err := GetUser(-1)
if err != nil {
fmt.Println("Error:", err)
// Type assertion for custom error
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field: %s\n", valErr.Field)
}
// Comparison with sentinel error
if errors.Is(err, ErrNotFound) {
fmt.Println("User not found")
}
} else {
fmt.Println("User:", user)
}
}Desde Go 1.13, se utiliza errors.Is() para comparar con errores centinela y errors.As() para extraer un tipo de error específico de una cadena de errores envueltos.
Goroutines: concurrencia ligera
Las goroutines son hilos ligeros gestionados por el runtime de Go. Lanzar una goroutine cuesta solo unos pocos KB de memoria.
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Decrement counter when done
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
// Launch 5 goroutines
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg) // 'go' prefix launches the goroutine
}
// Wait for all goroutines to complete
wg.Wait()
fmt.Println("All workers completed")
}El sync.WaitGroup permite esperar a que múltiples goroutines finalicen. Este es el patrón básico de paralelismo en Go.
Channels: comunicación entre goroutines
Los channels son conductos tipados para la comunicación entre goroutines. Permiten sincronización segura e intercambio de datos.
package main
import (
"fmt"
"time"
)
func producer(ch chan<- int) {
for i := 1; i <= 5; i++ {
fmt.Println("Producing:", i)
ch <- i // Send on channel
time.Sleep(100 * time.Millisecond)
}
close(ch) // Close channel when done
}
func consumer(ch <-chan int, done chan<- bool) {
for value := range ch { // Iterate until closed
fmt.Println("Consuming:", value)
}
done <- true
}
func main() {
ch := make(chan int) // Unbuffered channel
done := make(chan bool)
go producer(ch)
go consumer(ch, done)
<-done // Wait for consumer to finish
fmt.Println("All done")
}Channels con buffer y select
package main
import (
"fmt"
"time"
)
func main() {
// Buffered channel (capacity 3)
buffered := make(chan int, 3)
buffered <- 1
buffered <- 2
buffered <- 3
// buffered <- 4 // Would block since buffer is full
fmt.Println(<-buffered) // 1
// Select: channel multiplexing
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 for first available message
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received:", msg1)
case msg2 := <-ch2:
fmt.Println("Received:", msg2)
case <-time.After(500 * time.Millisecond):
fmt.Println("Timeout!")
}
}
}Go sigue el modelo CSP (Communicating Sequential Processes): "No comunicar compartiendo memoria; compartir memoria comunicando". Los channels previenen condiciones de carrera.
Testing en Go
Go incluye un framework de testing minimalista pero eficaz. Los archivos de test terminan en _test.go.
package calculator
func Add(a, b int) int {
return a + b
}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}package calculator
import (
"testing"
)
// Basic test
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
}
}
// Table-driven tests (recommended pattern)
func TestAddTableDriven(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -2, -3, -5},
{"zero", 0, 0, 0},
{"mixed", -5, 10, 5},
}
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)
}
})
}
}
// Error test
func TestDivideByZero(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Error("Expected error for division by zero")
}
}
// Benchmark
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(100, 200)
}
}Los tests se ejecutan con go test ./... y los benchmarks con go test -bench=..
HTTP: servidor web minimalista
Go sobresale en la creación de servidores HTTP de alto rendimiento utilizando su biblioteca estándar.
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
// Simple route
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Go!"))
})
// JSON route
http.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
})
// Route with method
http.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
w.Write([]byte("Get user"))
case "POST":
w.Write([]byte("Create user"))
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}Este servidor maneja miles de conexiones concurrentes gracias a las goroutines. Cada petición se procesa automáticamente en su propia goroutine.
Conclusión
Go ofrece un enfoque pragmático para el desarrollo backend: sintaxis simple, compilación rápida, concurrencia nativa y herramientas excelentes. Para desarrolladores de Java o Python, la transición requiere aceptar algunas convenciones diferentes (manejo explícito de errores, genéricos limitados antes de Go 1.18), pero los beneficios en rendimiento y mantenibilidad son inmediatos.
Lista de verificación para empezar
- ✅ Instalar Go desde el sitio oficial o gestor de paquetes
- ✅ Dominar los comandos
go build,go run,go test,go fmt - ✅ Entender la diferencia entre slices y arrays
- ✅ Adoptar el patrón
if err != nilpara el manejo de errores - ✅ Utilizar goroutines y channels para la concurrencia
- ✅ Escribir table-driven tests con el paquete
testing
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
El ecosistema Go es maduro, con frameworks populares como Gin, Echo y Fiber para desarrollo web, y herramientas como Cobra para CLIs. Con estos fundamentos sólidos, explorar temas avanzados como los genéricos (Go 1.18+), el paquete context y los patrones de concurrencia se vuelve accesible.
Etiquetas
Compartir
Artículos relacionados

Entrevista Tecnica de Go: Goroutines, Channels y Concurrencia
Guia completa con las preguntas mas frecuentes sobre goroutines, channels y concurrencia en Go. Incluye ejemplos de codigo listos para produccion, patrones de concurrencia y las respuestas que los entrevistadores esperan en 2026.

Preguntas de entrevista Node.js Backend: Guia completa 2026
Las 25 preguntas mas frecuentes en entrevistas de backend Node.js. Event loop, async/await, streams, clustering y rendimiento explicados con respuestas detalladas.

Spring Boot 3.4: Todas las novedades explicadas
Spring Boot 3.4 trae logging estructurado nativo, virtual threads extendidos, graceful shutdown por defecto y MockMvcTester. Guía completa de las nuevas funcionalidades.