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
mainand amain()function. - Use
importto bring in standard library or third-party packages.
Example:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Current time is:", time.Now())
}
Here:
fmtprovides formatted I/O (Println, Printf, etc.).timeprovides 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,float64stringboolrune(Unicode code point)byte(alias foruint8)
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:
&valuegives the address ofvalue.*numdereferences 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 <- 1sends a value into the channel.<-jobsreceives a value.jobs <-chan intmeans the channel is receive-only for that function.results chan<- intmeans 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/hellohttp://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.