Go Programming from Scratch with 25 Examples

Go from Scratch with 25 Examples

Go (Golang) is a language developed at Google. It is simple, compiles quickly and supports concurrency built in, which makes it popular for servers, tools and cloud systems. This article has 25 code examples, each run with Go 1.27, with the real output of each one. The examples move from the basics to concurrency, web handlers and generics.

What you need

Install Go from go.dev/dl and check with `go version`. Create a `.go` file and run it with `go run filename.go`.

Illustrations

The diagrams below show the workflow and the idea of goroutines with channels (schematic drawings, not real screenshots).
The diagrams below show the workflow and the idea of goroutines with channels (schematic drawings, not real screenshots).

Example 1: Hello, world

The simplest Go program. Every program starts with the main package and a main function.

package main

import "fmt"

func main() {
	fmt.Println("สวัสดี Go")
}

**Real output**

สวัสดี Go

Example 2: Variables and constants

`:=` declares and assigns in one step, `var` declares explicitly, and `const` is for values that never change.

package main

import "fmt"

const schoolName = "โรงเรียนตัวอย่าง"

func main() {
	name := "สมชาย"
	var age int = 20
	fmt.Println(schoolName, name, age)
}

**Real output**

โรงเรียนตัวอย่าง สมชาย 20

Example 3: Zero values

A variable declared without a value gets the zero value of its type, such as 0, an empty string or false.

package main

import "fmt"

func main() {
	var n int
	var s string
	var b bool
	var f float64
	fmt.Printf("int=%d string=%q bool=%t float=%.1f\n", n, s, b, f)
}

**Real output**

int=0 string="" bool=false float=0.0

Example 4: if and else

Decisions with if and else if. Go does not need parentheses around the condition.

package main

import "fmt"

func main() {
	score := 72
	if score >= 80 {
		fmt.Println("A")
	} else if score >= 70 {
		fmt.Println("B")
	} else {
		fmt.Println("C")
	}
}

**Real output**

B

Example 5: for loops

Go has only one loop keyword, for, which can count or act like a while loop.

package main

import "fmt"

func main() {
	for i := 1; i <= 3; i++ {
		fmt.Println("นับ", i)
	}
	n := 0
	for n < 2 {
		n++
	}
	fmt.Println("n =", n)
}

**Real output**

นับ 1
นับ 2
นับ 3
n = 2

Example 6: switch

switch compares several cases and does not fall through to the next case unless you ask it to.

package main

import "fmt"

func main() {
	day := "เสาร์"
	switch day {
	case "เสาร์", "อาทิตย์":
		fmt.Println("วันหยุด")
	default:
		fmt.Println("วันทำงาน")
	}
}

**Real output**

วันหยุด

Example 7: Arrays and slices

Arrays have a fixed size. Slices grow with append and can be cut with s[a:b].

package main

import "fmt"

func main() {
	arr := [3]int{1, 2, 3}
	s := []int{10, 20}
	s = append(s, 30, 40)
	fmt.Println("array:", arr, "len:", len(arr))
	fmt.Println("slice:", s, "len:", len(s), "cap:", cap(s) >= 4)
	fmt.Println("ตัดช่วง:", s[1:3])
}

**Real output**

array: [1 2 3] len: 3
slice: [10 20 30 40] len: 4 cap: true
ตัดช่วง: [20 30]

Example 8: Maps

Maps store key-value pairs. The form `v, ok :=` checks whether a key exists.

package main

import "fmt"

func main() {
	scores := map[string]int{"สมชาย": 85, "สมหญิง": 72}
	scores["สมศักดิ์"] = 45
	if v, ok := scores["สมชาย"]; ok {
		fmt.Println("คะแนน:", v)
	}
	delete(scores, "สมหญิง")
	fmt.Println("จำนวน:", len(scores))
}

**Real output**

คะแนน: 85
จำนวน: 2

Example 9: Functions with several return values

A function can return several values. An error is usually returned as the last value.

package main

import (
	"errors"
	"fmt"
)

func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("หารด้วยศูนย์ไม่ได้")
	}
	return a / b, nil
}

func main() {
	q, err := divide(10, 2)
	fmt.Println("ผล:", q, "error:", err)
	_, err = divide(1, 0)
	fmt.Println("error:", err)
}

**Real output**

ผล: 5 error: <nil>
error: หารด้วยศูนย์ไม่ได้

Example 10: Closures

A function that remembers variables from its surrounding scope. Useful for counters and private state.

package main

import "fmt"

func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

func main() {
	next := counter()
	fmt.Println(next(), next(), next())
}

**Real output**

1 2 3

Example 11: Structs and methods

A struct groups several pieces of data, and methods attach behaviour to that type.

package main

import "fmt"

type Student struct {
	Name  string
	Score int
}

func (s Student) Grade() string {
	if s.Score >= 80 {
		return "A"
	}
	return "B"
}

func main() {
	s := Student{Name: "สมชาย", Score: 85}
	fmt.Println(s.Name, s.Grade())
}

**Real output**

สมชาย A

Example 12: Interfaces

An interface lists the methods a type must have. Any type with those methods satisfies the interface automatically.

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Rect struct{ W, H float64 }

func (r Rect) Area() float64 { return r.W * r.H }

func main() {
	var s Shape = Rect{W: 3, H: 4}
	fmt.Printf("พื้นที่ = %.1f\n", s.Area())
}

**Real output**

พื้นที่ = 12.0

Example 13: Pointers

&x gives the address of a variable and *p reads or changes the value at that address, so a function can change caller data.

package main

import "fmt"

func increment(n *int) {
	*n++
}

func main() {
	x := 5
	increment(&x)
	fmt.Println("x =", x)
}

**Real output**

x = 6

Example 14: Handling errors

Create errors with fmt.Errorf and %w, then check with errors.Is to see whether an error matches a known one.

package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("ไม่พบข้อมูล")

func find(id int) error {
	if id != 1 {
		return fmt.Errorf("รหัส %d: %w", id, ErrNotFound)
	}
	return nil
}

func main() {
	err := find(2)
	fmt.Println(err)
	fmt.Println("ตรงกับ ErrNotFound:", errors.Is(err, ErrNotFound))
}

**Real output**

รหัส 2: ไม่พบข้อมูล
ตรงกับ ErrNotFound: true

Example 15: defer

defer postpones a call until the surrounding function returns. It suits closing files and releasing resources.

package main

import "fmt"

func main() {
	defer fmt.Println("ทำงานสุดท้าย")
	fmt.Println("เริ่มต้น")
	fmt.Println("ประมวลผล")
}

**Real output**

เริ่มต้น
ประมวลผล
ทำงานสุดท้าย

Example 16: panic and recover

panic stops normal execution, and recover can catch it only inside a deferred function, so the program does not crash.

package main

import "fmt"

func safeDivide(a, b int) (result int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("กู้คืนจาก panic: %v", r)
		}
	}()
	return a / b, nil
}

func main() {
	fmt.Println(safeDivide(10, 2))
	fmt.Println(safeDivide(1, 0))
}

**Real output**

5 <nil>
0 กู้คืนจาก panic: runtime error: integer divide by zero

Example 17: Goroutines

The go keyword starts work that runs concurrently. sync.WaitGroup waits until all the work is done.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			_ = n * n
		}(i)
	}
	wg.Wait()
	fmt.Println("ทุกงานเสร็จแล้ว")
}

**Real output**

ทุกงานเสร็จแล้ว

Example 18: Channels

Channels pass values between goroutines. This example sends values from a goroutine to main, then closes the channel.

package main

import "fmt"

func main() {
	ch := make(chan int)
	go func() {
		for i := 1; i <= 3; i++ {
			ch <- i * 10
		}
		close(ch)
	}()
	for v := range ch {
		fmt.Println("ได้รับ", v)
	}
}

**Real output**

ได้รับ 10
ได้รับ 20
ได้รับ 30

Example 19: select and timeouts

select waits on several channels at once. time.After sets a maximum waiting time.

package main

import (
	"fmt"
	"time"
)

func main() {
	result := make(chan string)
	go func() {
		time.Sleep(200 * time.Millisecond)
		result <- "เสร็จ"
	}()
	select {
	case msg := <-result:
		fmt.Println(msg)
	case <-time.After(50 * time.Millisecond):
		fmt.Println("หมดเวลา")
	}
}

**Real output**

หมดเวลา

Example 20: Mutex for shared data

sync.Mutex lets many goroutines change one variable safely. The total is always correct.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	var wg sync.WaitGroup
	total := 0
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			mu.Lock()
			total++
			mu.Unlock()
		}()
	}
	wg.Wait()
	fmt.Println("รวม =", total)
}

**Real output**

รวม = 100
The diagrams below show the workflow and the idea of goroutines with channels (schematic drawings, not real screenshots).
The diagrams below show the workflow and the idea of goroutines with channels (schematic drawings, not real screenshots).

Example 21: strings package and runes

strings provides text functions. len counts bytes, so count Thai characters by converting to []rune.

package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "Go เขียนง่าย"
	fmt.Println(strings.ToUpper("go"), strings.Contains(s, "เขียน"))
	fmt.Println(strings.Split("a,b,c", ","), len("สวัสดี"))
	fmt.Println([]rune("สวัสดี")[0] == 'ส')
}

**Real output**

GO true
[a b c] 18
true

Example 22: Reading and writing files

os.WriteFile and os.ReadFile write and read a whole file in one call.

package main

import (
	"fmt"
	"os"
)

func main() {
	if err := os.WriteFile("note.txt", []byte("บันทึกจาก Go\n"), 0644); err != nil {
		fmt.Println("เขียนไม่ได้:", err)
		return
	}
	data, err := os.ReadFile("note.txt")
	if err != nil {
		fmt.Println("อ่านไม่ได้:", err)
		return
	}
	fmt.Print("อ่านได้: ", string(data))
}

**Real output**

อ่านได้: บันทึกจาก Go

Example 23: JSON

encoding/json converts a struct to JSON and back. Tags such as `json:"..."` set the field names.

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

func main() {
	b, _ := json.Marshal(Student{Name: "สมชาย", Score: 85})
	fmt.Println(string(b))

	var s Student
	_ = json.Unmarshal([]byte(`{"name":"สมหญิง","score":72}`), &s)
	fmt.Println(s.Name, s.Score)
}

**Real output**

{"name":"สมชาย","score":85}
สมหญิง 72

Example 24: Testing an HTTP handler

httptest tests a handler function without starting a real server.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "สวัสดีจาก Go HTTP")
}

func main() {
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	hello(rec, req)
	body, _ := io.ReadAll(rec.Result().Body)
	fmt.Println(rec.Code, string(body))
}

**Real output**

200 สวัสดีจาก Go HTTP

Example 25: Generics

Functions that work on several types using a type parameter T, constrained by an interface.

package main

import "fmt"

type Number interface {
	~int | ~float64
}

func Sum[T Number](values []T) T {
	var total T
	for _, v := range values {
		total += v
	}
	return total
}

func main() {
	fmt.Println(Sum([]int{1, 2, 3}))
	fmt.Println(Sum([]float64{1.5, 2.5}))
}

**Real output**

6
4

Next steps

Try writing a web server that receives JSON and saves it to a file. Then study the net/http and context packages for timeouts and cancellation.

Summary

Go has a simple syntax, checks types at compile time, handles errors as values, and supports concurrency with goroutines and channels, which makes it a good fit for servers and workloads with many tasks.