Skip to content
Home
Go Programming: Getting Started with Go

Go Programming: Getting Started with Go

Go Go 10 min read 1921 words Intermediate ExcellentWiki Editorial Team

Go (often called Golang) is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. It was created to address the challenges of modern software development: fast compilation, efficient concurrency, and readability at scale. Since its release in 2012, Go has become the backbone of cloud infrastructure, powering tools like Docker, Kubernetes, Terraform, and Prometheus. Its adoption continues to grow, with companies like Uber, Twitch, Dropbox, and Cloudflare running critical production services in Go.

This guide walks you through everything you need to get started — from installation to writing your first real program.

Why Learn Go?

Go was designed with simplicity and productivity in mind. Here is what makes it stand out:

  • Fast compilation — A full Go project compiles in seconds. The compiler catches unused imports and variables at build time, preventing a whole class of runtime bugs.
  • Built-in concurrency — Goroutines and channels make concurrent programming more accessible than threads and locks in other languages.
  • Single binary output — Go compiles to a static binary with no external dependencies. Copy one file to a server and it runs.
  • Excellent standard libraryHTTP servers, JSON handling, templating, cryptography, and file I/O are all built-in. You can build production web services without third-party libraries.
  • Static typing with type inference — You get the safety of static types without verbose type annotations everywhere.
  • Cross-platform compilation — Build binaries for any target OS and architecture from a single machine. Set GOOS=linux GOARCH=arm64 and compile for a Raspberry Pi from your macOS laptop.

Go is particularly strong for CLI tools, API servers, networking services, and infrastructure software. It is less suited for GUI applications or systems programming that requires fine-grained memory control.

Installing Go

Go installation is straightforward on all major platforms.

Linux

wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz

Add /usr/local/go/bin to your PATH in ~/.bashrc or ~/.zshrc:

export PATH=$PATH:/usr/local/go/bin

macOS

brew install go

Windows

Download the MSI installer from go.dev/dl and run it. The installer automatically adds Go to your system PATH.

Verify your installation:

go version
# go version go1.22.5 linux/amd64

Your First Go Program

Go code is organized into packages. The entry point for any executable program is the main package with a main() function.

Create a new file called hello.go:

package main

import "fmt"

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

Run it directly:

go run hello.go
# Hello, Go!

Or compile it to a binary:

go build hello.go
./hello
# Hello, Go!

The go run command compiles and executes in one step — great for development. The go build command produces a standalone binary you can distribute. This binary is statically linked by default, meaning it will run on any Linux system of the same architecture without any dependencies installed.

Understanding the Go Module System

Modern Go uses modules for dependency management. A module is defined by a go.mod file at the root of your project.

Initialize a Module

mkdir myproject
cd myproject
go mod init myproject

This creates a go.mod file:

module myproject

go 1.22

This file tracks your module path and the Go version. When you import external packages, their versions are recorded here.

Project Structure

A typical Go project looks like this:

myproject/
├── go.mod          # Module definition
├── go.sum          # Dependency checksums
├── main.go         # Entry point
├── handler/
│   └── handler.go  # Package "handler"
└── models/
    └── user.go     # Package "models"

Every directory (except the root) is a separate package. Files in the same directory must share the same package name. This directory-as-package convention keeps the compiler simple and the project layout predictable.

Basic Syntax Tour

Variables and Types

Go supports both explicit and shorthand variable declarations:

package main

import "fmt"

func main() {
    // Explicit declaration
    var name string = "Alice"

    // Type inference
    var age = 30

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

    // Multiple variables
    var x, y int = 10, 20

    fmt.Println(name, age, city, x, y)
---

Basic types include string, int, float64, bool, byte, and rune. Go does not have implicit type conversion — you must convert explicitly:

var i int = 42
var f float64 = float64(i)

Functions

Functions return one or more values:

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("division by zero")
    }
    return a / b, nil
---

The multiple return value pattern is idiomatic in Go — the second value is almost always an error type. Callers check this value instead of using try-catch exceptions. Named return values are also supported and can improve documentation:

func getCoordinates() (x, y int) {
    x = 10
    y = 20
    return // naked return — returns x, y
---

Control Flow

Go has no while or ternary operators. for is the only looping keyword:

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

// While-style
count := 0
for count < 5 {
    fmt.Println(count)
    count++
---

// Infinite loop (break to exit)
for {
    // ...
    break
---

// Range loop (most common in practice)
nums := []int{10, 20, 30}
for index, value := range nums {
    fmt.Printf("nums[%d] = %d\n", index, value)
---

Conditionals look familiar but with one major difference — no parentheses around the condition, and you can declare variables scoped to the block:

// No parentheses needed
if x > 10 {
    fmt.Println("x is large")
---

// Scoped variable
if result, err := divide(10, 0); err != nil {
    fmt.Println("Error:", err)
--- else {
    fmt.Println("Result:", result)
---

The switch statement in Go is more flexible than in C — it does not fall through by default and accepts any type:

switch day {
case "Monday", "Tuesday":
    fmt.Println("Weekday")
case "Saturday", "Sunday":
    fmt.Println("Weekend")
default:
    fmt.Println("Invalid day")
---

Pointers

Go supports pointers but not pointer arithmetic. A pointer holds the memory address of a value:

var x int = 42
var p *int = &x  // p points to x
fmt.Println(*p)  // 42 — dereference
*p = 21          // modify x through p
fmt.Println(x)   // 21

Pointers are commonly used to avoid copying large structs and to allow functions to modify their arguments.

Structs and Methods

Go uses structs (not classes) to group data, and methods can be attached to any type:

type Rectangle struct {
    Width  float64
    Height float64
---

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
---

func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
---

rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area())  // 50
rect.Scale(2)
fmt.Println(rect.Area())  // 200

Methods with pointer receivers can modify the struct. Methods with value receivers operate on a copy.

Concurrency with Goroutines

Goroutines are lightweight threads managed by the Go runtime. They are one of Go’s signature features.

package main

import (
    "fmt"
    "time"
)

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

func main() {
    go greet("Hello")
    go greet("World")

    // Wait for goroutines to finish
    time.Sleep(1 * time.Second)
---

Launching a function with go runs it concurrently. For real synchronization, use channels or sync.WaitGroup instead of time.Sleep.

Channels

Channels let goroutines communicate safely:

func main() {
    messages := make(chan string)

    go func() {
        messages <- "ping"
    }()

    msg := <-messages
    fmt.Println(msg) // ping
---

Channels can be buffered or unbuffered. Unbuffered channels synchronize: a send blocks until a receive is ready. Buffered channels decouple send and receive up to the buffer capacity:

ch := make(chan int, 3) // buffered channel with capacity 3
ch <- 1
ch <- 2
ch <- 3
// ch <- 4 // would block — buffer full

Select Statement

The select statement lets a goroutine wait on multiple channel operations:

select {
case msg := <-messages:
    fmt.Println("Received:", msg)
case <-timeout:
    fmt.Println("Timeout")
default:
    fmt.Println("No messages ready")
---

Tooling and Ecosystem

Go’s toolchain is one of its strongest assets. The standard distribution includes:

  • gofmt — automatically formats Go source code to a canonical style. No tabs-vs-spaces debates; the tool decides.
  • go vet — static analysis that catches suspicious constructs like unreachable code, mismatched printf arguments, and lock copies.
  • go test — built-in test runner with coverage, benchmarking, and fuzzing support.
  • go mod — dependency management with cryptographic integrity verification.
  • go doc — documentation server that extracts comments from source code.

The gofmt tool is particularly important because it eliminates style discussions in code review. When all code in the ecosystem looks the same, reading unfamiliar projects becomes much easier. Most editors integrate gofmt to run on save, ensuring consistent formatting without manual effort.

Debugging

Go includes a built-in debugger, delve, which supports breakpoints, stack traces, and variable inspection:

go install github.com/go-delve/delve/cmd/dlv@latest
dlv debug main.go

For profiling, use pprof:

import _ "net/http/pprof"

// Access profiling at /debug/pprof/

Next Steps

Go has a deliberately small language specification — you can read it in an afternoon. The power comes from its standard library, tooling (gofmt, go vet, go test), and its opinionated approach to code organization.

Try these next:

  • Build a simple HTTP server using net/http
  • Write unit tests with the testing package
  • Explore the io.Reader and io.Writer interfaces
  • Benchmark your code with go test -bench

The Go community follows the proverb “A little copying is better than a little dependency.” Before reaching for an external package, check whether the standard library already covers your use case — it usually does.

FAQ

Q: Do I need a framework to build web apps in Go? A: No. The standard library includes a production-grade HTTP server, JSON encoding, templating, and all the tools needed for web development. Frameworks like Gin or Echo add convenience but are not required.

Q: How does Go handle dependency management? A: Go uses modules (go.mod files) with semantic versioning. Run go mod init to create a module, go get to add dependencies, and go mod tidy to clean up.

Q: Is Go garbage collected? A: Yes, Go has a concurrent, low-latency garbage collector designed for production workloads. GC pauses are typically under 1 millisecond.

Q: Can I use Go for web frontends? A: Go compiles to WebAssembly, so you can write frontend code with Go that runs in the browser. However, JavaScript/TypeScript remain the dominant frontend languages.

Q: What is the Go module proxy? A: The Go module proxy (proxy.golang.org) caches module versions and provides fast, reliable downloads. It is enabled by default and protects against dependency removal from the original source.

Q: How does Go compare to Python for backend development? A: Go is typically 10-100x faster than Python for CPU-bound tasks, uses less memory, and produces a single static binary. Python has a richer ecosystem for data science and machine learning.

Q: What is GOPATH and do I still need it? A: GOPATH was the original workspace concept before modules. With Go modules (Go 1.11+), you do not need to set GOPATH. Your code can live anywhere on disk.

Q: How do I handle errors properly in Go? A: Always check returned errors. Use errors.Is() for sentinel errors and errors.As() for custom error types. Use fmt.Errorf("context: %w", err) to wrap errors with context.

Q: Is Go suitable for microservices? A: Yes, Go is one of the most popular languages for microservices. Its fast startup time, low memory footprint, and single-binary deployment make it ideal for containerized environments.

For a comprehensive overview, read our article on Go Concurrency Guide.

For a comprehensive overview, read our article on Go Concurrency.

Section: Go 1921 words 10 min read Intermediate 756 articles in section Report inaccuracy Back to top