Go Essentials

Go (also known as Golang) is a statically typed, compiled programming language designed at Google. It combines the performance of compiled languages like C with the simplicity of modern languages. Go has become the language of choice for cloud-native applications, microservices, DevOps tooling, and platform engineering. This post covers the essential concepts needed to start building applications in Go.

What is Go

Go was created to address shortcomings in other languages while working on large-scale software systems. It emphasizes simplicity, readability, and strong support for concurrent programming.

Key characteristics:

graph LR A[Go Source Code] --> B[Go Compiler] B --> C[Native Binary] C --> D[Windows/Linux/macOS] C --> E[Cross-Platform]

Basic Syntax and Data Types

Variables and Constants

package main import "fmt" func main() { // Variable declaration var name string = "Alice" var age int = 30 // Type inference var city = "New York" // Short variable declaration (most common) country := "USA" // Multiple variables var x, y int = 1, 2 a, b := 3, 4 // Constants const Pi = 3.14159 const AppName = "MyApp" fmt.Println(name, age, city, country) }

Basic Types

// Boolean var isActive bool = true // Numeric types var count int = 42 // Platform-dependent size var age int8 = 127 // 8-bit signed var population uint64 = 1000000 // 64-bit unsigned var price float64 = 19.99 // 64-bit floating point var discount float32 = 0.1 // 32-bit floating point // String var message string = "Hello, Go!" // Rune (Unicode code point) var letter rune = 'A' // alias for int32 // Byte (alias for uint8) var b byte = 255

Functions

Functions are first-class citizens in Go.

// Basic function func greet(name string) { fmt.Println("Hello,", name) } // Function with return value func add(a int, b int) int { return a + b } // Multiple return values func divide(a, b float64) (float64, error) { if b == 0 { return 0, fmt.Errorf("cannot divide by zero") } return a / b, nil } // Named return values func calculate(a, b int) (sum int, product int) { sum = a + b product = a * b return // naked return uses named values } // Variadic function func sum(numbers ...int) int { total := 0 for _, num := range numbers { total += num } return total } // Usage func main() { greet("Alice") result := add(5, 3) quotient, err := divide(10, 2) if err != nil { fmt.Println("Error:", err) } total := sum(1, 2, 3, 4, 5) }
graph TD A[Function Call] --> B{Return Values} B -->|Single| C[value] B -->|Multiple| D[value1, value2, ...] B -->|With Error| E[result, error] E --> F{Check Error} F -->|nil| G[Use Result] F -->|not nil| H[Handle Error]

Control Flow

Conditionals

// If statement score := 85 if score >= 90 { fmt.Println("Grade: A") } else if score >= 80 { fmt.Println("Grade: B") } else { fmt.Println("Grade: C") } // If with initialization if value := compute(); value > 0 { fmt.Println("Positive:", value) } // Switch statement day := "Monday" switch day { case "Monday": fmt.Println("Start of week") case "Friday": fmt.Println("End of week") default: fmt.Println("Midweek") } // Switch with no condition (replaces if-else chains) switch { case score >= 90: fmt.Println("Excellent") case score >= 70: fmt.Println("Good") default: fmt.Println("Needs improvement") }

Loops

Go has only one loop construct: for.

// Traditional for loop for i := 0; i < 5; i++ { fmt.Println(i) } // While-style loop count := 0 for count < 5 { fmt.Println(count) count++ } // Infinite loop for { // Break out with: break if condition { break } } // Range over slice numbers := []int{1, 2, 3, 4, 5} for index, value := range numbers { fmt.Printf("Index: %d, Value: %d\n", index, value) } // Range with only values for _, value := range numbers { fmt.Println(value) } // Range over map ages := map[string]int{"Alice": 30, "Bob": 25} for name, age := range ages { fmt.Printf("%s is %d years old\n", name, age) }

Arrays and Slices

Arrays

Arrays have fixed size.

// Array declaration var arr [5]int arr[0] = 1 // Array literal numbers := [5]int{1, 2, 3, 4, 5} // Let compiler count nums := [...]int{1, 2, 3, 4, 5} // Get array length length := len(numbers)

Slices

Slices are dynamic, flexible views into arrays.

// Slice declaration var slice []int // Make slice with initial capacity slice = make([]int, 5) // length 5, capacity 5 slice = make([]int, 5, 10) // length 5, capacity 10 // Slice literal numbers := []int{1, 2, 3, 4, 5} // Append to slice numbers = append(numbers, 6) numbers = append(numbers, 7, 8, 9) // Slice a slice subset := numbers[1:4] // elements at index 1, 2, 3 // Copy slice dest := make([]int, len(numbers)) copy(dest, numbers) // Check length and capacity fmt.Println("Length:", len(numbers)) fmt.Println("Capacity:", cap(numbers))

Maps

Maps are key-value stores.

// Map declaration var m map[string]int // Make map m = make(map[string]int) // Map literal ages := map[string]int{ "Alice": 30, "Bob": 25, "Carol": 35, } // Add/update element ages["David"] = 40 // Get element age := ages["Alice"] // Check if key exists age, exists := ages["Alice"] if exists { fmt.Println("Age:", age) } // Delete element delete(ages, "Bob") // Iterate over map for name, age := range ages { fmt.Printf("%s: %d\n", name, age) } // Get length count := len(ages)
graph LR A[Map] --> B[Key: Value] A --> C[Key: Value] A --> D[Key: Value] B --> E[Fast Lookup] C --> E D --> E

Structs

Structs are typed collections of fields.

// Define struct type Person struct { FirstName string LastName string Age int } // Create struct var p Person p.FirstName = "Alice" p.LastName = "Smith" p.Age = 30 // Struct literal person := Person{ FirstName: "Bob", LastName: "Jones", Age: 25, } // Short form (must match order) person2 := Person{"Carol", "Brown", 35} // Anonymous struct config := struct { Host string Port int }{ Host: "localhost", Port: 8080, }

Methods

Methods are functions with a receiver.

type Rectangle struct { Width float64 Height float64 } // Value receiver func (r Rectangle) Area() float64 { return r.Width * r.Height } // Pointer receiver (can modify) func (r *Rectangle) Scale(factor float64) { r.Width *= factor r.Height *= factor } // Usage func main() { rect := Rectangle{Width: 10, Height: 5} area := rect.Area() fmt.Println("Area:", area) rect.Scale(2) fmt.Println("Scaled:", rect.Width, rect.Height) }
graph TD A[Struct Type] --> B[Value Receiver Method] A --> C[Pointer Receiver Method] B --> D[Read-only Operations] C --> E[Modify Struct]

Interfaces

Interfaces define behavior.

// Define interface type Shape interface { Area() float64 Perimeter() float64 } // Rectangle implements Shape 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 implements Shape type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius } func (c Circle) Perimeter() float64 { return 2 * 3.14159 * c.Radius } // Function accepting interface func printInfo(s Shape) { fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter()) } // Usage func main() { rect := Rectangle{Width: 10, Height: 5} circle := Circle{Radius: 7} printInfo(rect) printInfo(circle) }

Error Handling

Go uses explicit error handling.

import ( "errors" "fmt" ) // Function returning error func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } // Custom error type type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } // Using errors func main() { result, err := divide(10, 2) if err != nil { fmt.Println("Error:", err) return } fmt.Println("Result:", result) // Custom error err = validateAge(-5) if err != nil { fmt.Println(err) } } func validateAge(age int) error { if age < 0 { return &ValidationError{ Field: "age", Message: "must be positive", } } return nil }

Pointers

Pointers hold memory addresses.

// Declare pointer var p *int // Get address with & num := 42 p = &num // Dereference with * value := *p fmt.Println("Value:", value) // Modify through pointer *p = 100 fmt.Println("Updated num:", num) // Structs and pointers type Person struct { Name string Age int } func updateAge(p *Person, newAge int) { p.Age = newAge // Go automatically dereferences } func main() { person := Person{Name: "Alice", Age: 30} updateAge(&person, 31) fmt.Println(person.Age) // 31 }

Packages and Imports

// Package declaration (main package for executables) package main // Import single package import "fmt" // Import multiple packages import ( "fmt" "strings" "time" ) // Import with alias import ( f "fmt" str "strings" ) // Import for side effects only import _ "database/sql/driver" // Creating a custom package // File: math/operations.go package math func Add(a, b int) int { return a + b } // Exported (public) function starts with capital letter // Unexported (private) function starts with lowercase func multiply(a, b int) int { return a * b }

Practical Example: Simple HTTP Server

package main import ( "encoding/json" "fmt" "log" "net/http" ) type User struct { ID int `json:"id"` Name string `json:"name"` Age int `json:"age"` } var users = []User{ {ID: 1, Name: "Alice", Age: 30}, {ID: 2, Name: "Bob", Age: 25}, } func getUsers(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(users) } func home(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Welcome to Go API!") } func main() { http.HandleFunc("/", home) http.HandleFunc("/users", getUsers) fmt.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }

Key Takeaways