Go: Grundlagen für Java/Python-Entwickler in 2026
Go schnell erlernen durch vorhandene Java- oder Python-Erfahrung. Goroutines, Channels, Interfaces und wesentliche Patterns für einen reibungslosen Umstieg.

Go (oder Golang) hat sich als Sprache der Wahl für Microservices, CLI-Tools und verteilte Systeme etabliert. 2009 von Google entwickelt, vereint Go die Einfachheit von Python mit der Performance von C. Für Entwickler mit Java- oder Python-Hintergrund verläuft der Umstieg auf Go überraschend reibungslos, sobald die Kernkonzepte verstanden sind.
Go treibt Docker, Kubernetes, Terraform und unzählige Cloud-Infrastrukturen an. Die schnelle Kompilierung, native Concurrency-Unterstützung und das Deployment als einzelne Binary machen Go ideal für moderne Backend-Entwicklung.
Installation und Einrichtung von Go
Die Installation von Go ist unkompliziert und plattformübergreifend einheitlich. Das go-Tool verwaltet Kompilierung, Abhängigkeiten und Tests.
# 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/amd64Die Projektstruktur in Go folgt strikten, aber einfachen Konventionen. Die Datei go.mod definiert das Modul und seine Abhängigkeiten.
# 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 automaticallyErstes Go-Programm
Hier ist ein einfaches Programm, das die grundlegende Syntax von Go zeigt. Der Vergleich mit Java und Python hilft, die Unterschiede zu erkennen.
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)
}Was sofort auffällt: keine Semikolons, keine Klammern um Bedingungen und Typinferenz mit :=. Go setzt auf Prägnanz, ohne die Lesbarkeit zu opfern.
Variablen und grundlegende Typen
Go ist statisch typisiert, bietet aber hervorragende Typinferenz. Die Basistypen decken die meisten Anwendungsfälle ab.
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)
}Anders als in Java oder Python initialisiert Go Variablen automatisch mit ihrem "Zero Value": 0 für Zahlen, "" für Strings, false für Bools, nil für Pointer und Slices.
Funktionen und mehrfache Rückgabewerte
Go ermöglicht die Rückgabe mehrerer Werte — eine Eigenschaft, die intensiv für die Fehlerbehandlung genutzt wird.
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 und Methoden
Structs sind die Bausteine für benutzerdefinierte Typen in Go. Methoden werden über Receiver an Typen gebunden.
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())
}Ein Pointer-Receiver (*User) wird verwendet, wenn die Methode den Zustand ändert oder das Struct groß ist. Ein Value-Receiver (User) eignet sich für schreibgeschützte Methoden bei leichtgewichtigen Structs.
Interfaces: impliziter Polymorphismus
Go-Interfaces werden implizit implementiert. Ein Typ erfüllt ein Interface, wenn er alle Methoden implementiert — ohne explizite Deklaration.
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)
}
}Dieser Ansatz unterscheidet sich grundlegend von Java, wo implements obligatorisch ist. In Go ist die Konformität strukturell, nicht nominal.
Bereit für deine Go-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
Slices und Maps: dynamische Collections
Slices sind dynamische Sichten auf Arrays und Maps sind native Hash-Tabellen.
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)
}
}Idiomatische Fehlerbehandlung
Go kennt keine Exceptions. Fehler sind explizit zurückgegebene Werte, was eine rigorose Behandlung erzwingt.
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)
}
}Seit Go 1.13 wird errors.Is() zum Vergleich mit Sentinel-Fehlern und errors.As() zum Extrahieren eines bestimmten Fehlertyps aus einer Fehler-Kette verwendet.
Goroutines: leichtgewichtige Concurrency
Goroutines sind leichtgewichtige Threads, die von der Go-Runtime verwaltet werden. Das Starten einer Goroutine kostet nur wenige KB Speicher.
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")
}Die sync.WaitGroup ermöglicht das Warten auf den Abschluss mehrerer Goroutines. Dies ist das grundlegende Pattern für Parallelismus in Go.
Channels: Kommunikation zwischen Goroutines
Channels sind typisierte Kanäle für die Kommunikation zwischen Goroutines. Sie ermöglichen sichere Synchronisation und Datenaustausch.
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")
}Gepufferte Channels und 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 folgt dem CSP-Modell (Communicating Sequential Processes): "Kommuniziere nicht durch gemeinsamen Speicher; teile Speicher durch Kommunikation." Channels verhindern Race Conditions.
Testing in Go
Go enthält ein minimalistisches, aber effektives Test-Framework. Testdateien enden mit _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)
}
}Tests werden mit go test ./... ausgeführt und Benchmarks mit go test -bench=..
HTTP: minimalistischer Webserver
Go glänzt bei der Erstellung hochperformanter HTTP-Server mit seiner Standardbibliothek.
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))
}Dieser Server verarbeitet dank Goroutines Tausende gleichzeitiger Verbindungen. Jede Anfrage wird automatisch in ihrer eigenen Goroutine verarbeitet.
Fazit
Go bietet einen pragmatischen Ansatz für die Backend-Entwicklung: einfache Syntax, schnelle Kompilierung, native Concurrency und ausgezeichnete Tools. Für Java- oder Python-Entwickler erfordert der Umstieg die Akzeptanz einiger anderer Konventionen (explizite Fehlerbehandlung, eingeschränkte Generics vor Go 1.18), doch die Vorteile bei Performance und Wartbarkeit zeigen sich sofort.
Checkliste für den Einstieg
- ✅ Go über die offizielle Website oder den Paketmanager installieren
- ✅ Die Befehle
go build,go run,go test,go fmtbeherrschen - ✅ Den Unterschied zwischen Slices und Arrays verstehen
- ✅ Das
if err != nil-Pattern für Fehlerbehandlung übernehmen - ✅ Goroutines und Channels für Concurrency einsetzen
- ✅ Table-driven Tests mit dem
testing-Paket schreiben
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Das Go-Ökosystem ist ausgereift, mit populären Frameworks wie Gin, Echo und Fiber für Web-Entwicklung sowie Tools wie Cobra für CLIs. Mit diesen soliden Grundlagen wird die Auseinandersetzung mit fortgeschrittenen Themen wie Generics (Go 1.18+), dem Context-Paket und Concurrency-Patterns gut zugänglich.
Tags
Teilen
Verwandte Artikel

Go-Interview: Goroutines, Channels und Concurrency-Patterns meistern
Go-Interviewfragen zu Goroutines, Channels und Concurrency mit Codebeispielen. Fan-Out/Fan-In, Worker Pools, Race Conditions und Context-Patterns.

Node.js Backend Interview-Fragen: Vollständiger Leitfaden 2026
Die 25 häufigsten Node.js Backend Interview-Fragen. Event Loop, async/await, Streams, Clustering und Performance mit ausführlichen Antworten erklärt.

Spring Boot 3.4: Alle Neuerungen im Detail
Spring Boot 3.4 bringt natives strukturiertes Logging, erweiterte Virtual Threads, standardmäßiges Graceful Shutdown und MockMvcTester. Vollständiger Leitfaden der neuen Funktionen.