🚀 Go Cheatsheet: Your Fast-Track to Development in a Day

Welcome to the world of Go (often referred to as Golang)! This powerful, open-source programming language developed at Google is renowned for its simplicity, efficiency, and strong support for concurrent programming. Whether you're building high-performance web services, robust command-line tools, or scalable network applications, Go provides the elegance and speed you need. This cheatsheet is designed to give you a solid foundation in Go's core concepts, allowing you to confidently start developing in just one day.

✨ Why Go?

Go stands out with its:

  • Simplicity: Clean syntax, easy to read and write.
  • Concurrency: Built-in primitives (goroutines, channels) make concurrent programming a breeze.
  • Performance: Compiles to machine code, leading to fast execution.
  • Strong Tooling: Excellent built-in tools for testing, formatting, and dependency management.
  • Static Typing: Catches many errors at compile time.

1. 🛠️ Getting Started: Installation & Workspace

First things first, you need Go installed on your machine. Visit golang.org/doc/install for detailed instructions specific to your operating system. After installation, verify it by running:


go version

You should see something like go version go1.22.2 linux/amd64. Go doesn't require a complex setup for workspaces anymore; you can work from any directory. For simple single-file execution, just navigate to your file's directory.

2. ✍️ Basic Syntax & Structure

Every Go program starts with a package declaration and a `main` function if it's an executable.

Package and Main Function

The package main declaration tells the Go compiler that this file is an executable program. The func main() is the entry point of your program.


package main // Declares the package as 'main', making it an executable program

import "fmt" // Imports the 'fmt' package for formatted I/O

func main() { // The main function, where program execution begins
    fmt.Println("Hello, Go!") // Prints a string to the console
}

To run this, save it as `hello.go` and use `go run hello.go` in your terminal.

Variables and Constants

Go is statically typed. You declare variables using var, or use the short declaration operator := for type inference.


package main

import "fmt"

func main() {
    // Variable declaration with 'var'
    var name string = "Alice"
    var age int = 30
    fmt.Println("Name:", name, "Age:", age)

    // Short variable declaration (type inference)
    message := "Welcome to Go!"
    pi := 3.14159
    fmt.Println(message, pi)

    // Declare multiple variables
    var x, y int = 10, 20
    fmt.Println("x:", x, "y:", y)

    // Constants are immutable
    const GREETING = "Hello"
    // GREETING = "Hi" // This would cause a compile-time error
    fmt.Println(GREETING)
}

Important: In Go, a variable declared but not used will result in a compile-time error. This helps keep code clean!

Basic Data Types

  • bool: true or false
  • Numeric types: int, int8, int16, int32, int64, uint (unsigned integers), float32, float64, complex64, complex128, byte (alias for uint8), rune (alias for int32, represents a Unicode code point).
  • string: UTF-8 encoded sequences of characters.

Printing Output (`fmt` Package)


package main

import "fmt"

func main() {
    name := "Charlie"
    age := 25

    fmt.Println("Hello,", name, "You are", age, "years old.") // Prints values separated by spaces, adds newline
    fmt.Printf("Hello, %s! You are %d years old.\n", name, age)   // Formatted printing (like C's printf)
    fmt.Println(fmt.Sprintf("This message can be stored in a variable: %s", name)) // Sprintf returns a string
}

💡 Key Point: `fmt.Println` vs `fmt.Printf`

Println is for simple printing with spaces and newlines. Printf gives you full control over formatting using format verbs (e.g., %s for string, %d for decimal integer, %f for float, %v for default value, %T for type).

3. 🚦 Control Flow

If/Else Statements

Go's if statements do not require parentheses around the condition. Curly braces are mandatory.


package main

import "fmt"

func main() {
    num := 10

    if num > 0 {
        fmt.Println("Positive")
    } else if num < 0 {
        fmt.Println("Negative")
    } else {
        fmt.Println("Zero")
    }

    // 'if' with a short statement (variable scope is within if/else block)
    if val := 5; val%2 == 0 {
        fmt.Println(val, "is even")
    } else {
        fmt.Println(val, "is odd")
    }
    // fmt.Println(val) // This would be an error, 'val' is not defined here
}

For Loops

Go has only one looping construct: for. It's incredibly versatile.


package main

import "fmt"

func main() {
    // Classic C-style for loop
    for i := 0; i < 5; i++ {
        fmt.Print(i, " ")
    }
    fmt.Println() // Newline

    // 'While' loop style (condition only)
    sum := 1
    for sum < 1000 {
        sum += sum
    }
    fmt.Println("Sum (while style):"), sum)

    // Infinite loop (break inside to exit)
    count := 0
    for {
        fmt.Println("Looping...")
        count++
        if count >= 3 {
            break // Exits the loop
        }
    }

    // For-range loop (iterating over collections)
    numbers := []int{10, 20, 30}
    for index, value := range numbers {
        fmt.Printf("Index: %d, Value: %d\n", index, value)
    }

    // If you only need value, ignore index with '_'
    for _, value := range numbers {
        fmt.Println("Value (no index):"), value)
    }
}

Switch Statements

Go's switch statements automatically break after a match (no fallthrough by default). Use fallthrough explicitly if needed.


package main

import "fmt"

func main() {
    day := "Tuesday"

    switch day {
    case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
        fmt.Println("It's a weekday.")
    case "Saturday", "Sunday":
        fmt.Println("It's the weekend!")
    default:
        fmt.Println("Invalid day.")
    }

    // Switch without an expression (acts like if/else if chain)
    score := 85
    switch {
    case score >= 90:
        fmt.Println("Grade A")
    case score >= 80:
        fmt.Println("Grade B")
    default:
        fmt.Println("Grade C or lower")
    }
}

4. 🧩 Functions

Functions are fundamental building blocks. Go functions can return multiple values.


package main

import "fmt"

// Basic function declaration
func greet(name string) {
    fmt.Println("Hello,", name)
}

// Function returning a single value
func add(a int, b int) int { // (a int, b int) can be (a, b int)
    return a + b
}

// Function returning multiple values
func swap(x, y string) (string, string) {
    return y, x
}

// Function with named return values (zero-valued on entry, returned on 'return')
func divide(numerator, denominator float64) (quotient float64, err error) {
    if denominator == 0 {
        err = fmt.Errorf("cannot divide by zero")
        return // Naked return, returns quotient (0) and err
    }
    quotient = numerator / denominator
    return
}

// Variadic function (accepts zero or more arguments of a specific type)
func sumAll(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    greet("Dave")
    result := add(5, 3)
    fmt.Println("5 + 3 =", result)

    a, b := swap("world", "hello")
    fmt.Println(a, b) // Output: hello world

    q1, err1 := divide(10, 2)
    if err1 != nil {
        fmt.Println("Error:", err1)
    } else {
        fmt.Println("Quotient:", q1)
    }

    q2, err2 := divide(10, 0)
    if err2 != nil {
        fmt.Println("Error:", err2)
    } else {
        fmt.Println("Quotient:", q2)
    }

    fmt.Println("Sum of 1,2,3:", sumAll(1, 2, 3))
    fmt.Println("Sum of 5,10,15,20:", sumAll(5, 10, 15, 20))
}

Pro-Tip: Go encourages explicit error handling by returning errors as the last return value. This makes errors impossible to ignore without compiler warnings.

5. 📊 Data Structures

Arrays

Arrays are fixed-size sequences of elements of the same type. Their size is part of their type.


package main

import "fmt"

func main() {
    var a [3]int // Declares an array 'a' of 3 integers, initialized to zeros
    fmt.Println("Array a:", a)

    a[0] = 10
    a[1] = 20
    a[2] = 30
    fmt.Println("Array a (modified):"), a)
    fmt.Println("Length of a:", len(a))

    b := [4]string{"apple", "banana", "cherry", "date"} // Array literal
    fmt.Println("Array b:", b)
}

Slices

Slices are dynamic, flexible views into elements of a particular type. They are built on top of arrays.


package main

import "fmt"

func main() {
    // Declaring a slice (no fixed size)
    var s []string
    fmt.Println("Empty slice:", s, len(s), cap(s))

    // Using make to create a slice with initial length and capacity
    // make([]type, len, cap)
    numbers := make([]int, 3, 5) // len=3, cap=5
    fmt.Println("Numbers slice:", numbers, len(numbers), cap(numbers))

    // Slice literal
    fruits := []string{"apple", "banana", "cherry"}
    fmt.Println("Fruits:", fruits)

    // Appending elements (may reallocate if capacity is exceeded)
    fruits = append(fruits, "date")
    fmt.Println("Fruits after append:", fruits)
    fruits = append(fruits, "elderberry", "fig") // Append multiple
    fmt.Println("Fruits after more appends:", fruits)

    // Slicing an existing slice/array
    primes := [6]int{2, 3, 5, 7, 11, 13}
    var s2 []int = primes[1:4] // slice from index 1 (inclusive) to 4 (exclusive)
    fmt.Println("Sliced primes:", s2) // Output: [3 5 7]
}

💡 Analogy: Slices vs. Arrays

Think of an array as a fixed-size shelf. You decide its length when you buy it, and it never changes.

A slice is like a flexible conveyor belt. It can expand or shrink as items are added or removed, internally managing the underlying space (the 'shelf') for you.

Maps

Maps are unordered collections of key-value pairs, similar to dictionaries or hash tables.


package main

import "fmt"

func main() {
    // Declaring a map using make()
    ages := make(map[string]int) // map[keyType]valueType

    ages["Alice"] = 30
    ages["Bob"] = 25
    fmt.Println("Ages map:", ages)

    // Map literal
    colors := map[string]string{
        "red":    "#FF0000",
        "green":  "#00FF00",
        "blue":   "#0000FF",
    }
    fmt.Println("Colors map:", colors)

    // Accessing a value
    fmt.Println("Alice's age:", ages["Alice"])

    // Checking if a key exists (idiomatic 'comma ok' syntax)
    if val, ok := ages["Charlie"]; ok {
        fmt.Println("Charlie's age:", val)
    } else {
        fmt.Println("Charlie not found in ages map.")
    }

    // Deleting an element
    delete(ages, "Bob")
    fmt.Println("Ages after deleting Bob:", ages)
}

Structs

Structs are typed collections of fields. They're like classes without methods (methods are defined separately for structs).


package main

import "fmt"

// Define a struct type
type Person struct {
    Name string
    Age  int
    City string
}

func main() {
    // Create a struct instance
    p1 := Person{Name: "Eve", Age: 28, City: "New York"}
    fmt.Println("Person 1:", p1)

    // Access fields
    fmt.Println("Name:", p1.Name)
    fmt.Println("Age:", p1.Age)

    // Create a struct with some fields omitted (zero-valued)
    p2 := Person{Name: "Frank"}
    fmt.Println("Person 2:", p2) // Age and City will be zero-valued (0, "")

    // Pointer to a struct
    pp := &p1 // pp is a pointer to p1
    fmt.Println("Person 1 via pointer:", (*pp).Name) // Dereference explicitly
    fmt.Println("Person 1 via pointer (shorthand):"), pp.Name) // Go automatically dereferences

    // Anonymous struct (useful for temporary, one-off structs)
    anon := struct {
        ID int
        Data string
    }{
        ID: 123,
        Data: "secret info",
    }
    fmt.Println("Anonymous struct:", anon)
}

6. 📍 Pointers

Go has pointers, but no pointer arithmetic. They let you refer to the memory address of a value.


package main

import "fmt"

func modifyValue(val *int) {
    *val = 200 // Dereference 'val' and assign a new value to the memory address it points to
}

func main() {
    a := 100
    fmt.Println("Initial value of a:", a) // Output: 100

    p := &a // p now holds the memory address of a
    fmt.Println("Value of a through pointer p:", *p) // Dereference p to get the value: 100
    fmt.Println("Memory address of a (stored in p):"), p) // Output: a memory address like 0xc0000140a8

    *p = 150 // Change value at address p points to
    fmt.Println("Value of a after modifying via pointer:", a) // Output: 150

    modifyValue(&a) // Pass the memory address of 'a' to the function
    fmt.Println("Value of a after function call:", a) // Output: 200
}

Key Use Case: Pointers are primarily used to allow functions to modify the value of a variable declared in the calling function, or to avoid copying large data structures when passing them around.

7. 🚨 Error Handling

Go doesn't have exceptions. Instead, functions typically return an error as their last return value. If the operation succeeds, the error is nil; otherwise, it's a non-nil error value.


package main

import (
    "errors"
    "fmt"
)

// A function that might return an error
func safeDivide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("cannot divide by zero") // Return zero-value for result and a new error
    }
    return a / b, nil // Return result and nil (no error)
}

func main() {
    result, err := safeDivide(10, 2)
    if err != nil { // Always check for errors
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result of division:", result)
    }

    result, err = safeDivide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result of division:", result)
    }

    // Creating custom errors with fmt.Errorf (often preferred for adding context)
    errCustom := fmt.Errorf("failed to process item %d: %w", 123, errors.New("item not found"))
    fmt.Println("Custom error:", errCustom)
}

Idiom: Always check the error value immediately after a function call that might return an error. This is a core Go principle.

8. ⚡ Concurrency: Goroutines & Channels

Go's built-in concurrency primitives are a major highlight.

Goroutines

A goroutine is a lightweight thread of execution managed by the Go runtime. Use the go keyword to launch one.


package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 3; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world") // Launch 'say("world")' as a goroutine
    say("hello")    // Run 'say("hello")' in the main goroutine

    // The main goroutine needs to stay alive for other goroutines to complete.
    // In real applications, you'd use wait groups or channels to synchronize.
    time.Sleep(time.Second) // Give goroutines time to finish
    fmt.Println("Done!")
}

Channels

Channels are the primary way goroutines communicate. They allow data to be sent and received between goroutines.


package main

import (
    "fmt"
)

func sum(s []int, c chan int) {
    total := 0
    for _, v := range s {
        total += v
    }
    c <- total // Send total to channel c
}

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // Create a channel for integers
    c := make(chan int)

    // Split the slice and sum parts in separate goroutines
    go sum(numbers[:len(numbers)/2], c)
    go sum(numbers[len(numbers)/2:], c)

    // Receive from channel (this blocks until a value is sent)
    x, y := <-c, <-c // Receive values from the channel

    fmt.Println("Sum of first half:", x)
    fmt.Println("Sum of second half:", y)
    fmt.Println("Total sum:", x + y)

    // Buffered Channels
    // make(chan int, 2) creates a buffered channel with capacity 2
    // Sending to a buffered channel blocks only when the buffer is full.
    // Receiving blocks when the buffer is empty.
    bc := make(chan string, 2)
    bc <- "message 1"
    bc <- "message 2"
    // bc <- "message 3" // This would block because buffer is full

    fmt.Println("From buffered channel:", <-bc)
    fmt.Println("From buffered channel:", <-bc)

    // Closing a channel
    // Only the sender should close a channel, never the receiver.
    // Sending on a closed channel will cause a panic.
    // Receiving from a closed channel returns the zero value and a 'comma ok' of false.
    sendOnly := make(chan int, 1)
    sendOnly <- 100
    close(sendOnly)

    val, ok := <-sendOnly
    fmt.Printf("Received: %d, Open: %t\n", val, ok) // Output: Received: 100, Open: true

    val, ok = <-sendOnly // Receive again from a closed channel
    fmt.Printf("Received: %d, Open: %t\n", val, ok) // Output: Received: 0, Open: false (zero value, not open)

    // Iterating over a channel using 'range'
    queue := make(chan string, 3)
    queue <- "one"
    queue <- "two"
    close(queue)

    for elem := range queue {
        fmt.Println("Queue element:", elem)
    }
}

💡 Analogy: Goroutines & Channels

Imagine goroutines as individual workers. They work independently but often need to share tools or information.

Channels are like designated, safe pipelines or conveyor belts between these workers. They ensure information is passed orderly and without race conditions.

9. 🔄 Interfaces

Go's interfaces are implicit. A type implements an interface by simply providing all the methods declared in the interface. There's no explicit `implements` keyword.


package main

import "fmt"

// Define an interface
type Greeter interface {
    Greet() string
}

// Define a struct type
type EnglishSpeaker struct {
    Name string
}

// Implement the Greeter interface for EnglishSpeaker
func (es EnglishSpeaker) Greet() string {
    return "Hello, my name is " + es.Name
}

// Another struct type
type SpanishSpeaker struct {
    Name string
}

// Implement the Greeter interface for SpanishSpeaker
func (ss SpanishSpeaker) Greet() string {
    return "Hola, mi nombre es " + ss.Name
}

// A function that accepts any type that implements the Greeter interface
func sayGreeting(g Greeter) {
    fmt.Println(g.Greet())
}

func main() {
    e := EnglishSpeaker{Name: "John"}
    s := SpanishSpeaker{Name: "Maria"}

    sayGreeting(e)
    sayGreeting(s)

    // The empty interface `interface{}` can hold values of any type.
    // Use with caution, as it loses type safety.
    var i interface{}
    i = "a string"
    fmt.Printf("Value: %v, Type: %T\n", i, i)

    i = 42
    fmt.Printf("Value: %v, Type: %T\n", i, i)
}

Duck Typing: If it walks like a duck and quacks like a duck, it's a duck. If a type has all the methods an interface requires, it *is* that interface.

10. 📦 Modules & Packages

Go uses modules for dependency management. A module is a collection of packages that are released together.

Creating a Module

Navigate to your project directory and initialize a module:


mkdir myapp
cd myapp
go mod init github.com/yourusername/myapp

This creates a `go.mod` file, tracking dependencies.

Packages

Every Go file belongs to a package. Files in the same directory usually belong to the same package. Functions and variables starting with an uppercase letter are exported (public) and can be accessed from other packages. Lowercase names are unexported (private) to their package.


// Inside a file named 'math_ops.go' in a package called 'mymath'
package mymath

// Exported function
func Add(a, b int) int {
    return a + b
}

// Unexported function
func subtract(a, b int) int {
    return a - b
}

// Inside main.go (in package main)
package main

import (
    "fmt"
    "github.com/yourusername/myapp/mymath" // Import your custom package
)

func main() {
    // Access exported function
    sum := mymath.Add(10, 5)
    fmt.Println("Sum:", sum)

    // This would be a compile error: mymath.subtract is not exported
    // diff := mymath.subtract(10, 5)
}

11. ⚙️ Essential Go Tools

  • go run main.go: Compiles and runs the specified Go source file(s). Quick for single files.
  • go build: Compiles your package and its dependencies. Creates an executable binary in the current directory (or $GOBIN).
  • go test: Runs tests in your package. Go has a built-in testing framework.
  • go fmt: Formats your Go source code according to the official Go style (Go 'fmt' is opinionated, saving you style debates!).
  • go mod tidy: Cleans up unused dependencies and adds missing ones to go.mod.
  • go get [package_path]: Downloads and installs packages and their dependencies. Used to add new dependencies to your module.

🎉 Conclusion: You're Ready to Go!

Congratulations! You've just traversed the core concepts of Go programming. In a single day, you've grasped the fundamentals of its syntax, control flow, data structures, error handling, and even its powerful concurrency model. Go's design philosophy prioritizes simplicity and efficiency, making it a joy to work with once you get the hang of its idioms.

💡 Next Steps:

  • Practice: The best way to learn is by doing. Try solving small problems on platforms like LeetCode or HackerRank using Go.
  • Go Tour: Work through the official Go Tour for an interactive learning experience.
  • Explore Standard Library: Go's standard library is extensive and well-documented. Look into packages like net/http for web servers, os for file system operations, and encoding/json for JSON manipulation.
  • Build Something: Start a small personal project. A simple API, a command-line utility, or a network scanner will solidify your knowledge.

Go is a fantastic language for building robust and scalable applications. With this cheatsheet as your starting point, you're well-equipped to dive deeper and harness its full potential. Happy coding!

Take a Quiz Based on This Article

Test your understanding with AI-generated questions tailored to this content

(1-15)
Go
Golang
Programming
Cheatsheet
Beginner
Concurrency
Development
Tutorial