Getting Started with Go: A Practical Beginner’s Guide

Go (often called Golang) is a modern programming language designed at Google. It focuses on simplicity, performance, and built-in concurrency. If you want to …

Go (often called Golang) is a modern programming language designed at Google. It focuses on simplicity, performance, and built-in concurrency. If you want to build fast web services, CLIs, tools, or backend systems, Go is a great choice.

This article will walk you through Go from zero to a small, working example, with plenty of code you can copy, paste, and run.

1. What is Go and Why Use It?

Go is:

  • Compiled and statically typed — Fast, with early error detection.
  • Simple — A small language spec; easy to read other people’s code.
  • Batteries included — Rich standard library (HTTP, JSON, crypto, etc.).
  • Great at concurrency — Goroutines and channels are first-class features.
  • Cross-platform — Build binaries for Linux, macOS, Windows, etc.

Typical use cases:

  • Web services and APIs
  • CLI tools and automation
  • Microservices and backend systems
  • Networking tools and proxies

2. Installing Go

You can check if Go is installed:

go version

You should see something like:

go version go1.24.4 darwin/arm64

If not, download Go from the official website and follow the installer for your OS, then re-open your terminal and check again.

3. Your First Go Program

Let’s write a simple “Hello, Go” program.

Create a folder:

mkdir hello-go
cd hello-go

Initialize a Go module (this sets up dependency management):

go mod init example.com/hello-go

Now create main.go:

package main

import "fmt"

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

Run it:

go run .

Or:

go run main.go

You should see:

Hello, Go!

That’s your first Go program.

4. Basic Syntax: Packages, Imports, and main

  • Every Go file starts with a package name.
  • Executable programs must have package main and a main() function.
  • Use import to bring in standard library or third-party packages.

Example:

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("Current time is:", time.Now())
}

Here:

  • fmt provides formatted I/O (Println, Printf, etc.).
  • time provides time-related functions.

5. Variables, Types, and Constants

5.1 Variable Declarations

Go is statically typed but supports type inference.

package main

import "fmt"

func main() {
    // Explicit type
    var age int = 30

    // Type inference
    var name = "Alice"

    // Short declaration (only inside functions)
    city := "Berlin"

    fmt.Println("Name:", name, "Age:", age, "City:", city)
}

5.2 Basic Types

Common types:

  • int, int64, float64
  • string
  • bool
  • rune (Unicode code point)
  • byte (alias for uint8)
a := 10          // int
b := 3.14        // float64
c := "hello"     // string
d := true        // bool

5.3 Constants

Use const for values that don’t change:

const Pi = 3.14159
const AppName = "MyGoApp"

6. Control Flow: if, for, switch

6.1 if Statements

package main

import "fmt"

func main() {
    score := 85

    if score >= 90 {
        fmt.Println("Grade: A")
    } else if score >= 80 {
        fmt.Println("Grade: B")
    } else {
        fmt.Println("Grade: C or lower")
    }
}

You can also declare variables just for the if:

if length := len("hello"); length > 3 {
    fmt.Println("String is longer than 3")
}

6.2 for Loops

Go has only one loop keyword: for.

// Classic for
for i := 0; i < 5; i++ {
    fmt.Println("i =", i)
}

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

// Infinite loop (break manually)
for {
    fmt.Println("Looping...")
    break
}

6.3 switch

Switches are clean and powerful in Go.

package main

import "fmt"

func main() {
    day := "Monday"

    switch day {
    case "Monday":
        fmt.Println("Start of the week")
    case "Saturday", "Sunday":
        fmt.Println("Weekend!")
    default:
        fmt.Println("Midweek")
    }
}

7. Functions and Multiple Return Values

7.1 Basic Function

package main

import "fmt"

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

func main() {
    result := add(10, 20)
    fmt.Println("Result:", result)
}

You can shorten parameter types:

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

7.2 Multiple Return Values

Go functions can return multiple values, often used for results + errors.

package main

import (
    "errors"
    "fmt"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("cannot divide by zero")
    }
    return a / b, nil
}

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

8. Arrays, Slices, and Maps

8.1 Arrays

Fixed-size sequence of elements.

var nums [3]int
nums[0] = 10
nums[1] = 20
nums[2] = 30

But in Go, we usually prefer slices.

8.2 Slices

Slices are dynamic views over arrays.

package main

import "fmt"

func main() {
    // Literal
    nums := []int{10, 20, 30}
    fmt.Println(nums) // [10 20 30]

    // Append
    nums = append(nums, 40)
    fmt.Println(nums) // [10 20 30 40]

    // Range loop
    for i, v := range nums {
        fmt.Println("Index:", i, "Value:", v)
    }
}

8.3 Maps

Maps are key–value dictionaries.

package main

import "fmt"

func main() {
    ages := map[string]int{
        "Alice": 30,
        "Bob":   25,
    }

    ages["Charlie"] = 35

    // Access
    fmt.Println("Alice:", ages["Alice"])

    // Check existence
    age, ok := ages["Eve"]
    if !ok {
        fmt.Println("Eve not found")
    } else {
        fmt.Println("Eve:", age)
    }

    // Iterate
    for name, age := range ages {
        fmt.Println(name, "is", age)
    }
}

9. Structs and Methods

9.1 Defining a Struct

Structs group related fields together.

package main

import "fmt"

type User struct {
    ID    int
    Name  string
    Email string
    Age   int
}

func main() {
    u := User{
        ID:    1,
        Name:  "Alice",
        Email: "alice@example.com",
        Age:   30,
    }

    fmt.Printf("User: %+v\n", u)
}

9.2 Methods on Structs

Go doesn’t have classes, but you can define methods on structs.

type User struct {
    Name string
    Age  int
}

// Value receiver (copy)
func (u User) Greet() string {
    return fmt.Sprintf("Hi, I'm %s and I'm %d years old.", u.Name, u.Age)
}

// Pointer receiver (can modify the original)
func (u *User) HaveBirthday() {
    u.Age++
}

Usage:

func main() {
    u := User{Name: "Bob", Age: 20}
    fmt.Println(u.Greet())

    u.HaveBirthday()
    fmt.Println(u.Greet())
}

10. Pointers in Go

Pointers store the address of a value.

package main

import "fmt"

func increment(num *int) {
    *num = *num + 1
}

func main() {
    value := 10
    fmt.Println("Before:", value)

    increment(&value)
    fmt.Println("After:", value)
}

Notes:

  • &value gives the address of value.
  • *num dereferences the pointer to read/write the actual value.
  • Go doesn’t support pointer arithmetic (unlike C).

11. Interfaces: Behavior, Not Inheritance

Interfaces let you define behavior without specifying how it’s implemented.

package main

import "fmt"

// Interface
type Notifier interface {
    Notify(message string)
}

// Struct 1
type EmailNotifier struct {
    Email string
}

func (e EmailNotifier) Notify(message string) {
    fmt.Println("Sending EMAIL to", e.Email+":", message)
}

// Struct 2
type SMSNotifier struct {
    Phone string
}

func (s SMSNotifier) Notify(message string) {
    fmt.Println("Sending SMS to", s.Phone+":", message)
}

func sendAlert(n Notifier, message string) {
    n.Notify(message)
}

func main() {
    email := EmailNotifier{Email: "user@example.com"}
    sms := SMSNotifier{Phone: "+123456789"}

    sendAlert(email, "Welcome to Go!")
    sendAlert(sms, "Your code just compiled successfully.")
}

Any type that has a Notify(string) method implements Notifier automatically. No implements keyword needed.

12. Organizing Code with Packages and Modules

Go encourages clear, simple package structures.

Example project:

myapp/
  go.mod
  main.go
  user/
    user.go

user/user.go:

package user

type User struct {
    ID   int
    Name string
}

func New(id int, name string) User {
    return User{
        ID:   id,
        Name: name,
    }
}

main.go:

package main

import (
    "fmt"

    "example.com/myapp/user"
)

func main() {
    u := user.New(1, "Alice")
    fmt.Printf("User: %+v\n", u)
}

Your go.mod should have the module path:

module example.com/myapp

You can choose any module path, but it’s common to use a Git repo URL.

13. Concurrency Basics: Goroutines and Channels

One of Go’s biggest strengths is concurrency.

13.1 Goroutines

A goroutine is a lightweight thread managed by Go’s runtime.

package main

import (
    "fmt"
    "time"
)

func printNumbers(prefix string) {
    for i := 1; i <= 3; i++ {
        fmt.Println(prefix, i)
        time.Sleep(200 * time.Millisecond)
    }
}

func main() {
    go printNumbers("A") // run concurrently
    go printNumbers("B") // run concurrently

    // Keep main alive long enough to see output
    time.Sleep(1 * time.Second)
    fmt.Println("Done")
}

13.2 Channels

Channels are typed pipes you use to send values between goroutines.

package main

import (
    "fmt"
    "time"
)

func worker(jobs <-chan int, results chan<- int) {
    for job := range jobs {
        // Simulate work
        time.Sleep(100 * time.Millisecond)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 5)
    results := make(chan int, 5)

    // Start 2 workers
    go worker(jobs, results)
    go worker(jobs, results)

    // Send jobs and close the channel
    for i := 1; i <= 5; i++ {
        jobs <- i
    }
    close(jobs)

    // Collect results
    for i := 1; i <= 5; i++ {
        res := <-results
        fmt.Println("Result:", res)
    }
}

Notes:

  • jobs <- 1 sends a value into the channel.
  • <-jobs receives a value.
  • jobs <-chan int means the channel is receive-only for that function.
  • results chan<- int means send-only.

14. A Tiny HTTP API in Go

Let’s build a very small HTTP server to see how Go works in a realistic scenario.

Project structure:

mini-api/
  go.mod
  main.go

Initialize:

mkdir mini-api
cd mini-api
go mod init example.com/mini-api

main.go:

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "time"
)

// Response is a simple JSON structure
type Response struct {
    Message   string    `json:"message"`
    Timestamp time.Time `json:"timestamp"`
}

func main() {
    // Handle /hello route
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        resp := Response{
            Message:   "Hello from Go HTTP server!",
            Timestamp: time.Now(),
        }

        w.Header().Set("Content-Type", "application/json")
        if err := json.NewEncoder(w).Encode(resp); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    })

    // Simple health check
    http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        _, _ = w.Write([]byte("ok"))
    })

    addr := ":8080"
    log.Println("Server listening on", addr)
    if err := http.ListenAndServe(addr, nil); err != nil {
        log.Fatal("Server error:", err)
    }
}

Run:

go run .

Open in your browser:

  • http://localhost:8080/hello
  • http://localhost:8080/healthz

You’ll see JSON output for /hello and plain text ok for /healthz.

15. Building and Distributing Your Program

Once your code works, build a binary:

go build -o mini-api .

This produces an executable (mini-api or mini-api.exe on Windows). You can copy this binary to another machine with the same OS/architecture and run it directly.

You can also cross-compile by setting GOOS and GOARCH:

GOOS=linux GOARCH=amd64 go build -o mini-api-linux .

16. Where to Go Next

You now know:

  • How to write and run basic Go programs
  • Variables, control flow, functions
  • Slices, maps, structs, methods, interfaces
  • Basics of concurrency with goroutines and channels
  • How to build a small HTTP API

Next steps:

  • Explore the standard library (net/http, io, os, context, sync, time).
  • Learn about testing with go test.
  • Try building a CLI tool or a more complex web service.
  • Read Go’s official “Effective Go” and Go blog posts.

Keep Reading

Follow the engineering thread

Get the next practical Birdor note, or browse the archive for related systems, tooling, and architecture work.

Join newsletter Browse articles