🚀 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:trueorfalse- Numeric types:
int,int8,int16,int32,int64,uint(unsigned integers),float32,float64,complex64,complex128,byte(alias foruint8),rune(alias forint32, 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 togo.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/httpfor web servers,osfor file system operations, andencoding/jsonfor 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