Golang for Beginners: The Complete 2026 Guide

Golang for beginners: installation, syntax, data structures, goroutines. A step-by-step guide with code examples to get you started with Go development.
- tags
- #Golang #Go #Programming #For Beginners #Tutorial
- categories
- Programming
- published
Golang (or simply Go) is a programming language from Google that has taken over the backend development world. Docker, Kubernetes, Terraform — all of it is written in Go. And if you want to break into modern development, Golang is a great choice.
In this guide, I’ll walk you from zero to writing your first application. No fluff — just practice and real examples.
Why Golang?
Before diving into syntax, let’s figure out why you’d want to learn yet another language in the first place.
Easy to learn
Golang was built on the philosophy of “less is more.” There are no classes, no inheritance, no exceptions in the traditional sense. You can learn the entire language in a couple of weeks.
// Here's what Hello World looks like in Go
package main
import "fmt"
func main() {
fmt.Println("Привет, Golang!")
}
Performance
Go compiles to native code. That means speed close to C/C++, with the convenience of Python. No virtual machines, no JIT.
Built-in concurrency
Goroutines are Go’s killer feature. Want to run thousands of tasks in parallel? Easy. I wrote about this in detail in my article on concurrency in Go: channels vs sync .
A large community and plenty of jobs
Golang ranks in the top 10 most popular languages. There are plenty of job openings, salaries are high, and competition is lower than in Python or JavaScript.
Installing Golang
Windows
- Download the installer from the official website
- Run the .msi file
- Verify the installation:
go version
# go version go1.22.0 windows/amd64
macOS
# Via Homebrew (recommended)
brew install go
# Verify
go version
Linux
# Ubuntu/Debian
sudo apt update
sudo apt install golang-go
# Or download directly
wget https://golang.org/dl/go1.22.0.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
Environment setup
After installing, add the following to your .bashrc or .zshrc:
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
Your first Golang program
Let’s write the classic Hello World and break down every line.
package main
import "fmt"
func main() {
fmt.Println("Привет, Golang!")
}
Breaking down the code
package main— every Go file belongs to a package.mainis the program’s entry pointimport "fmt"— imports the standard package for formatted outputfunc main()— the main function, where execution beginsfmt.Println()— prints a string followed by a newline
Running the program
# Create a main.go file with the code above
go run main.go
# Привет, Golang!
# Or compile it into a binary
go build main.go
./main
Variables and data types
Go is a strictly typed language. That means every variable has a type, and the compiler enforces it.
Declaring variables
// Explicit type
var name string = "Алексей"
var age int = 30
// Short declaration (recommended)
name := "Алексей"
age := 30
// Multiple declaration
var (
firstName = "Алексей"
lastName = "Горбунов"
isActive = true
)
Basic types
// Numbers
var i int = 42 // int, int8, int16, int32, int64
var f float64 = 3.14 // float32, float64
var c complex128 = 1+2i // complex numbers
// Strings
var s string = "Hello"
// Booleans
var b bool = true
// Bytes and runes
var byte byte = 'A' // alias for uint8
var r rune = 'Я' // alias for int32 (Unicode)
Constants
const Pi = 3.14159
const (
StatusOK = 200
StatusError = 500
)
// iota — auto-increment for constants
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)
Golang data structures
Arrays
Arrays in Go have a fixed length:
// An array of 5 elements
var arr [5]int = [5]int{1, 2, 3, 4, 5}
// Short form
arr := [5]int{1, 2, 3, 4, 5}
// Auto-detect the length
arr := [...]int{1, 2, 3, 4, 5}
fmt.Println(arr[0]) // 1
fmt.Println(len(arr)) // 5
Slices
Slices are dynamic arrays, and they’re used far more often:
// Creating a slice
slice := []int{1, 2, 3, 4, 5}
// Via make
slice := make([]int, 5) // length 5
slice := make([]int, 5, 10) // length 5, capacity 10
// Adding elements
slice = append(slice, 6, 7, 8)
// Slicing an array
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2, 3, 4]
Maps
// Creating a map
m := map[string]int{
"apple": 5,
"banana": 3,
}
// Via make
m := make(map[string]int)
// Operations
m["orange"] = 7 // add
value := m["apple"] // read
delete(m, "banana") // delete
// Checking if a key exists
value, exists := m["apple"]
if exists {
fmt.Println("Найдено:", value)
}
Functions in Golang
Basic syntax
// A simple function
func greet(name string) {
fmt.Println("Привет,", name)
}
// A function with a return value
func add(a, b int) int {
return a + b
}
// Multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("деление на ноль")
}
return a / b, nil
}
Named return values
func rectangle(width, height float64) (area, perimeter float64) {
area = width * height
perimeter = 2 * (width + height)
return // naked return
}
Variadic functions
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
// Call
sum(1, 2, 3, 4, 5) // 15
Anonymous functions and closures
// Anonymous function
func() {
fmt.Println("Я анонимная!")
}()
// Closure
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
Structs and methods
Defining a struct
type User struct {
ID int
Name string
Email string
CreatedAt time.Time
}
// Creating an instance
user := User{
ID: 1,
Name: "Алексей",
Email: "alex@example.com",
}
// Accessing fields
fmt.Println(user.Name)
Struct methods
type Rectangle struct {
Width float64
Height float64
}
// A method with a value receiver
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// A method with a pointer receiver (can modify the struct)
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
rect := Rectangle{10, 5}
fmt.Println(rect.Area()) // 50
rect.Scale(2)
fmt.Println(rect.Area()) // 200
Embedding (composition)
type Person struct {
Name string
Age int
}
type Employee struct {
Person // embedding
Position string
Salary int
}
emp := Employee{
Person: Person{Name: "Алексей", Age: 30},
Position: "Developer",
Salary: 200000,
}
// Access Person's fields directly
fmt.Println(emp.Name) // Алексей
Interfaces in Golang
Interfaces are a powerful tool for abstraction. In Go, they’re implemented implicitly.
// Defining an interface
type Writer interface {
Write(data []byte) error
}
// A struct implementing the interface
type FileWriter struct {
filename string
}
func (fw FileWriter) Write(data []byte) error {
return os.WriteFile(fw.filename, data, 0644)
}
// Usage
func save(w Writer, data []byte) error {
return w.Write(data)
}
fw := FileWriter{"output.txt"}
save(fw, []byte("Hello!"))
The empty interface
// interface{} or any — accepts a value of any type
func printAnything(v interface{}) {
fmt.Printf("Тип: %T, Значение: %v\n", v, v)
}
printAnything(42)
printAnything("hello")
printAnything([]int{1, 2, 3})
Type assertion
var i interface{} = "hello"
// Type assertion
s := i.(string)
fmt.Println(s) // hello
// Safe assertion
s, ok := i.(string)
if ok {
fmt.Println("Это строка:", s)
}
// Type switch
switch v := i.(type) {
case string:
fmt.Println("Строка:", v)
case int:
fmt.Println("Число:", v)
default:
fmt.Println("Неизвестный тип")
}
Error handling
Go has no exceptions. Instead, errors are handled explicitly by returning an error value.
func readFile(filename string) ([]byte, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("не удалось прочитать файл %s: %w", filename, err)
}
return data, nil
}
// Usage
data, err := readFile("config.json")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
Creating your own errors
import "errors"
var ErrNotFound = errors.New("не найдено")
func findUser(id int) (*User, error) {
// ... lookup logic
if user == nil {
return nil, ErrNotFound
}
return user, nil
}
// Checking the error type
if errors.Is(err, ErrNotFound) {
fmt.Println("Пользователь не найден")
}
To learn how to avoid common mistakes when writing code, check out my article on Go anti-patterns .
Goroutines and channels
Concurrency is Go’s headline feature. Goroutines are lightweight threads, and channels are how they communicate with each other.
Goroutines
func sayHello(name string) {
fmt.Println("Привет,", name)
}
func main() {
// Launching a goroutine
go sayHello("Алексей")
go sayHello("Мария")
// Wait for them to finish (a bad approach)
time.Sleep(time.Second)
}
Channels
func main() {
// Creating a channel
ch := make(chan string)
// Sending from a goroutine
go func() {
ch <- "Сообщение из горутины"
}()
// Receiving
msg := <-ch
fmt.Println(msg)
}
Buffered channels
// A channel with a buffer of 3 elements
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
// ch <- 4 // would block, buffer is full
fmt.Println(<-ch) // 1
WaitGroup for synchronization
import "sync"
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Println("Горутина", n)
}(i)
}
wg.Wait() // wait for all goroutines to finish
fmt.Println("Все горутины завершены")
}
For more on synchronization and choosing between channels and sync, read my article on concurrency in Go .
Working with JSON
import "encoding/json"
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
// Serialization (struct -> JSON)
user := User{ID: 1, Name: "Алексей"}
data, err := json.Marshal(user)
// {"id":1,"name":"Алексей"}
// Deserialization (JSON -> struct)
var user2 User
err = json.Unmarshal(data, &user2)
Working with HTTP
HTTP server
import "net/http"
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Привет, %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", helloHandler)
fmt.Println("Сервер запущен на :8080")
http.ListenAndServe(":8080", nil)
}
HTTP client
resp, err := http.Get("https://api.example.com/users")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
Testing in Golang
Go has built-in tooling for testing. I’ve written about this in more detail in my article on testing in Go .
// math.go
package math
func Add(a, b int) int {
return a + b
}
// math_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d; want 5", result)
}
}
Running the tests:
go test ./...
go test -v ./... # verbose output
go test -cover # code coverage
Modules and dependencies
Initializing a module
mkdir myproject
cd myproject
go mod init github.com/username/myproject
Adding dependencies
go get github.com/gin-gonic/gin
go get github.com/lib/pq
The go.mod file
module github.com/username/myproject
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/lib/pq v1.10.9
)
What to learn next
Once you’ve got the basic syntax down, I recommend digging deeper into:
- Concurrency — patterns for working with goroutines and channels
- Testing — table-driven tests, mocks, benchmarks
- Web frameworks — Gin, Echo, Fiber
- Databases — GORM, sqlx
- gRPC — for microservice architectures (read my article on gRPC in Go )
Useful resources
- Official documentation
- Go by Example
- Effective Go
- Go Playground — a sandbox for experiments
Conclusion
Golang is a language that makes development enjoyable. Simple syntax, fast compilation, a powerful standard library. Start with simple projects — CLI tools, HTTP servers, parsers — and gradually move on to more complex tasks.
If you have questions, leave a comment. And don’t forget to check out my other articles on Go — there’s a lot of practical advice drawn from real-world experience.
Good luck learning Golang! 🚀