Skip to content
Go back

Learn Go in 60 Seconds

Go is a statically typed, compiled language developed by Google in 2009. Designed for simplicity, efficiency, and reliability, Go has become the language of choice for cloud infrastructure, microservices, and distributed systems. This guide will get you from zero to productive in Go in just 60 seconds.

Table of contents

Open Table of contents

Why Go?

Go was created to address the challenges of modern software development at scale. It combines the performance of compiled languages like C++ with the simplicity of interpreted languages like Python. Key advantages include:

Basic Syntax

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Variables and Types

Go is statically typed, meaning variable types are determined at compile time. Here are the different ways to declare and initialize variables:

// Explicit variable declaration with type
var name string = "Go"
var age int = 15
var price float64 = 99.99

// Type inference (Go infers the type)
var language = "Go"  // string
var version = 1.21   // float64

// Short declaration (most common)
count := 42
isActive := true
message := "Hello, World!"

// Multiple variables
var x, y int = 10, 20
a, b := "hello", "world"

// Zero values (default values for uninitialized variables)
var str string    // "" (empty string)
var num int       // 0
var flag bool     // false
var ptr *int      // nil

Common Data Types

// Basic types examples
var (
    // Integers
    age     int     = 25        // 4 bytes (32-bit) or 8 bytes (64-bit)
    count   int64   = 1000000   // 8 bytes
    small   int8    = 127       // 1 byte
    big     uint64  = 1844674   // 8 bytes

    // Floating point
    price   float64 = 19.99     // 8 bytes
    tax     float32 = 0.08      // 4 bytes

    // Complex numbers
    z1      complex64  = 1 + 2i // 8 bytes
    z2      complex128 = 3 + 4i // 16 bytes

    // Boolean
    isValid bool    = true      // 1 byte

    // String (header size shown, actual string data separate)
    name    string  = "Alice"   // 16 bytes header (64-bit)

    // Byte and rune
    data    byte    = 255       // 1 byte (alias for uint8)
    char    rune    = 'A'       // 4 bytes (alias for int32)
)

📚 Want the complete data types reference? Check out our comprehensive Complete Guide to Go Data Types and Memory Sizes for detailed information about all Go data types, their exact memory sizes, and practical examples.

Conditional Statements (if/else)

age := 18

if age >= 18 {
    fmt.Println("Adult")
} else if age >= 13 {
    fmt.Println("Teenager")
} else {
    fmt.Println("Child")
}

// If with initialization
if num := 10; num > 5 {
    fmt.Println("Greater than 5")
}

Functions

Functions in Go are first-class citizens and support multiple return values, which is commonly used for error handling:

// Basic function
func add(a, b int) int {
    return a + b
}

// Multiple return values (common pattern for error handling)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// Named return values
func calculate(x, y int) (sum, product int) {
    sum = x + y
    product = x * y
    return // naked return
}

// Variadic functions (variable number of arguments)
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

// Function as a variable
var multiply = func(a, b int) int {
    return a * b
}

// Usage examples
func main() {
    result := add(5, 3)                    // 8
    quotient, err := divide(10, 2)         // 5.0, nil
    s, p := calculate(4, 5)                // 9, 20
    total := sum(1, 2, 3, 4, 5)           // 15
    product := multiply(6, 7)              // 42
}

Arrays and Slices

Arrays have fixed size, while slices are dynamic and more commonly used in Go:

// Arrays (fixed size)
var arr [3]int = [3]int{1, 2, 3}
numbers := [5]int{1, 2, 3, 4, 5}
auto := [...]int{1, 2, 3} // compiler counts elements

// Slices (dynamic arrays - more common)
slice := []int{1, 2, 3, 4}
slice = append(slice, 5)              // [1, 2, 3, 4, 5]
slice = append(slice, 6, 7, 8)        // [1, 2, 3, 4, 5, 6, 7, 8]

// Creating slices
var empty []int                       // nil slice
made := make([]int, 5)               // [0, 0, 0, 0, 0]
withCap := make([]int, 3, 10)        // length 3, capacity 10

// Slicing operations
original := []int{0, 1, 2, 3, 4, 5}
sub := original[1:4]                  // [1, 2, 3]
prefix := original[:3]                // [0, 1, 2]
suffix := original[2:]                // [2, 3, 4, 5]

// Common slice operations
fmt.Println(len(slice))               // length
fmt.Println(cap(slice))               // capacity

Maps

Maps are Go’s built-in key-value data structure, similar to dictionaries or hash tables in other languages:

// Map declaration and initialization
ages := map[string]int{
    "Alice":   25,
    "Bob":     30,
    "Charlie": 35,
}

// Creating empty maps
var scores map[string]int              // nil map
scores = make(map[string]int)          // initialized empty map
grades := make(map[string]int)         // alternative syntax

// Adding and accessing values
ages["David"] = 28
age := ages["Alice"]                   // 25

// Safe access with existence check
age, exists := ages["Eve"]
if exists {
    fmt.Printf("Eve is %d years old\n", age)
} else {
    fmt.Println("Eve not found")
}

// Deleting entries
delete(ages, "Bob")

// Iterating over maps
for name, age := range ages {
    fmt.Printf("%s is %d years old\n", name, age)
}

// Map length
fmt.Printf("Map has %d entries\n", len(ages))

Structs

Structs are Go’s way to create custom data types and group related data together:

// Defining a struct
type Person struct {
    Name    string
    Age     int
    Email   string
    Address Address // embedded struct
}

type Address struct {
    Street  string
    City    string
    Country string
}

// Creating struct instances
p1 := Person{
    Name:  "John",
    Age:   30,
    Email: "john@example.com",
    Address: Address{
        Street:  "123 Main St",
        City:    "New York",
        Country: "USA",
    },
}

// Shorthand initialization (field order matters)
p2 := Person{"Alice", 25, "alice@example.com", Address{}}

// Accessing fields
fmt.Println(p1.Name)                    // John
fmt.Println(p1.Address.City)            // New York

// Struct methods
func (p Person) Greet() string {
    return fmt.Sprintf("Hello, I'm %s", p.Name)
}

// Pointer receiver (can modify the struct)
func (p *Person) HaveBirthday() {
    p.Age++
}

// Usage
fmt.Println(p1.Greet())                 // Hello, I'm John
p1.HaveBirthday()
fmt.Println(p1.Age)                     // 31

Go-Specific Concepts

Goroutines (Concurrency)

// Start a goroutine
go func() {
    fmt.Println("Running concurrently")
}()

Channels (Communication)

// Create a channel
ch := make(chan int)

// Send and receive
go func() {
    ch <- 42 // Send
}()

value := <-ch // Receive

Defer

func example() {
    defer fmt.Println("This runs last")
    fmt.Println("This runs first")
}

Error Handling

result, err := divide(10, 0)
if err != nil {
    fmt.Println("Error:", err)
    return
}
fmt.Println("Result:", result)

Key Features

Complete Example Project

Go playground

package main

import (
    "fmt"
    "time"
)

// Define a struct
type Task struct {
    ID   int
    Name string
    Done bool
}

// Method on struct
func (t *Task) Complete() {
    t.Done = true
    fmt.Printf("Task '%s' completed!\n", t.Name)
}

// Function that uses channels
func processTask(task Task, ch chan<- string) {
    // Simulate work
    time.Sleep(time.Millisecond * 100)

    if task.ID%2 == 0 {
        ch <- fmt.Sprintf("Task %d processed successfully", task.ID)
    } else {
        ch <- fmt.Sprintf("Task %d failed", task.ID)
    }
}

func main() {
    // Create tasks
    tasks := []Task{
        {1, "Write code", false},
        {2, "Test code", false},
        {3, "Deploy code", false},
    }

    // Channel for communication
    results := make(chan string, len(tasks))

    // Process tasks concurrently
    for _, task := range tasks {
        go processTask(task, results)
    }

    // Collect results
    for i := 0; i < len(tasks); i++ {
        result := <-results
        fmt.Println(result)
    }

    // Complete first task
    tasks[0].Complete()

    // Map example
    taskStatus := make(map[int]string)
    for _, task := range tasks {
        if task.Done {
            taskStatus[task.ID] = "completed"
        } else {
            taskStatus[task.ID] = "pending"
        }
    }

    // Print status
    fmt.Println("\nTask Status:")
    for id, status := range taskStatus {
        fmt.Printf("Task %d: %s\n", id, status)
    }

    // Conditional example
    totalTasks := len(tasks)
    if totalTasks > 5 {
        fmt.Println("Large project")
    } else if totalTasks > 2 {
        fmt.Println("Medium project")
    } else {
        fmt.Println("Small project")
    }
}

Loops and Control Flow

Go has only one loop construct - the for loop - but it’s versatile enough to handle all looping scenarios:

// Traditional for loop
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// While-style loop
count := 0
for count < 3 {
    fmt.Println("Count:", count)
    count++
}

// Infinite loop
for {
    // This runs forever (use break to exit)
    break
}

// Range loops (iterate over collections)
slice := []string{"apple", "banana", "cherry"}
for index, value := range slice {
    fmt.Printf("%d: %s\n", index, value)
}

// Range over map
ages := map[string]int{"Alice": 25, "Bob": 30}
for name, age := range ages {
    fmt.Printf("%s is %d\n", name, age)
}

// Ignore index with underscore
for _, value := range slice {
    fmt.Println(value)
}

// Switch statements
grade := "A"
switch grade {
case "A":
    fmt.Println("Excellent!")
case "B", "C":
    fmt.Println("Good job!")
case "D":
    fmt.Println("Needs improvement")
default:
    fmt.Println("Invalid grade")
}

// Switch without expression (like if-else chain)
score := 85
switch {
case score >= 90:
    fmt.Println("A")
case score >= 80:
    fmt.Println("B")
case score >= 70:
    fmt.Println("C")
default:
    fmt.Println("F")
}

Interfaces

Interfaces in Go are implicit - any type that implements all methods of an interface automatically satisfies that interface:

// Define an interface
type Writer interface {
    Write([]byte) (int, error)
}

type Speaker interface {
    Speak() string
}

// Struct that implements Speaker
type Dog struct {
    Name string
}

func (d Dog) Speak() string {
    return "Woof!"
}

type Cat struct {
    Name string
}

func (c Cat) Speak() string {
    return "Meow!"
}

// Function that accepts any Speaker
func makeSound(s Speaker) {
    fmt.Println(s.Speak())
}

// Usage
dog := Dog{Name: "Buddy"}
cat := Cat{Name: "Whiskers"}

makeSound(dog)  // Woof!
makeSound(cat)  // Meow!

// Empty interface can hold any value
var anything interface{}
anything = 42
anything = "hello"
anything = []int{1, 2, 3}

Best Practices

1. Error Handling

Always handle errors explicitly - don’t ignore them:

// Good: explicit error handling
file, err := os.Open("config.txt")
if err != nil {
    log.Fatal("Failed to open file:", err)
}
defer file.Close()

// Bad: ignoring errors
file, _ := os.Open("config.txt") // Don't do this!

2. Use defer for Cleanup

Use defer to ensure cleanup happens even if errors occur:

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close() // This will run when function exits

    // Process file...
    return nil
}

3. Naming Conventions

Follow Go’s naming conventions:

// Exported (public) - starts with capital letter
type User struct {
    Name  string // exported field
    email string // unexported field
}

func NewUser(name, email string) *User { // exported function
    return &User{Name: name, email: email}
}

// Unexported (private) - starts with lowercase
func validateEmail(email string) bool {
    // validation logic
    return true
}

4. Use Short Variable Names

Go encourages short, meaningful variable names:

// Good
for i, user := range users {
    fmt.Printf("%d: %s\n", i, user.Name)
}

// Acceptable for longer scope
func processUserData(userData []User) error {
    for _, user := range userData {
        if err := validateUser(user); err != nil {
            return err
        }
    }
    return nil
}

5. Composition over Inheritance

Go doesn’t have inheritance, use composition instead:

type Engine struct {
    Power int
}

func (e Engine) Start() {
    fmt.Println("Engine starting...")
}

type Car struct {
    Engine // embedded struct
    Brand  string
}

// Car automatically has Start() method
car := Car{
    Engine: Engine{Power: 200},
    Brand:  "Toyota",
}
car.Start() // Works!

References


Share this post on:

Previous Post
Creating UDP Client and Server in Go
Next Post
Go Data Types and Memory Sizes