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:
- Fast compilation: Compiles to native machine code quickly
- Statically typed: Type safety with type inference
- Garbage collected: Automatic memory management
- Built-in concurrency: Goroutines and channels for concurrent programming
- Simple syntax: Minimal language features, easy to learn
- Standard library: Comprehensive built-in packages
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
- Go is a simple, fast, compiled language designed for cloud-native development
- Variables can be declared with
varor short declaration:=for type inference - Functions support multiple return values, commonly used for result and error pairs
- Slices provide dynamic arrays, maps provide key-value storage
- Structs group related data, methods add behavior to structs
- Interfaces define behavior contracts implemented implicitly
- Error handling is explicit using error return values, not exceptions
- Pointers enable passing references to modify data efficiently
- Packages organize code, exported names start with capital letters
- Go's simplicity and strong standard library make it ideal for system tools and services
- The language excels at building CLI tools, microservices, and infrastructure software
- Understanding these essentials provides a solid foundation for Go development