Go Basics: Program Structure, Variables, Control Flow and Functions
Chapter 01 of 03

Go Basics: Program Structure, Variables, Control Flow and Functions

Start writing Go: how a program is structured, zero values, if and switch, the one-and-only for loop, multiple return values and error handling.

Sabbir Ahmmed4 min read0
On this page

Go was designed at Google for one job: building large, reliable server software that many engineers can read quickly. It deliberately has a small syntax. If you know one other language, you can learn the core of Go in a weekend, and this series walks through it from the ground up with runnable examples.

Anatomy of a Go program

Every executable Go program starts in package main, inside a function called main.

main.go
1
2
3
4
5
6
7
package main

import "fmt"

func main() {
	fmt.Println("Hello, Go!")
}
package main tells the compiler this package builds into a program, not a library.
import "fmt" brings in the standard formatting package (printing, string formatting).
func main() is the entry point. When it returns, the program exits.

Run it with go run main.go, or build a single static binary with go build.

bash
1
2
3
go mod init example.com/hello   # once per project: creates go.mod
go run .                         # compile and run
go build -o hello .              # produce a binary
Tip
Go formats code for you. Run gofmt -w . (or let your editor do it on save) and never argue about tabs or brace style again. Every Go codebase looks the same, which is a big part of why Go code is easy to read.

Variables and types

Go is statically typed, but it infers types so you rarely write them.

go
1
2
3
4
5
6
7
var name string = "Sabbir" // full form
var age = 28               // type inferred as int
city := "Khulna"           // short declaration, only inside functions

var count int      // zero value: 0
var ok bool        // zero value: false
var title string   // zero value: "" (empty string)

Every type has a zero value, so a declared variable is always usable. There is no "undefined" in Go. The short form := declares and assigns; you cannot use it to re-assign an existing variable on its own.

Note
Go refuses to compile if you declare a local variable or import a package and never use it. It feels strict at first, but it keeps code clean.

Control flow: if and switch

if needs no parentheses, but braces are mandatory.

go
1
2
3
4
5
6
7
8
num := 17
if num >= 18 {
	fmt.Println("Adult")
} else if num >= 15 {
	fmt.Println("Teenager")
} else {
	fmt.Println("Child")
}

An if can run a short statement first. The variable only lives inside the if/else block, which keeps scope tight:

go
1
2
3
if n, err := strconv.Atoi("42"); err == nil {
	fmt.Println("parsed", n)
}

switch is cleaner than long if chains. Unlike C or JavaScript, Go does not fall through to the next case, so you never need break. A case can also match several values.

go
1
2
3
4
5
6
7
8
9
a := 3
switch a {
case 1:
	fmt.Println("One")
case 2, 3:
	fmt.Println("Two or three")
default:
	fmt.Println("Something else")
}

A switch with no value is a tidy replacement for if / else if:

go
1
2
3
4
5
6
7
8
switch {
case score >= 80:
	grade = "A+"
case score >= 70:
	grade = "A"
default:
	grade = "B"
}

Loops: Go only has for

There is no while keyword. for covers every case:

go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for i := 0; i < 3; i++ { // classic
	fmt.Println(i)
}

n := 1
for n < 100 { // "while"
	n *= 2
}

for { // infinite loop, exit with break or return
	break
}

for i, v := range []string{"a", "b"} { // iterate a collection
	fmt.Println(i, v)
}

for i := range 3 { // range over an integer (Go 1.22+)
	fmt.Println(i) // 0 1 2
}

Functions and multiple return values

Parameters list the name first, then the type. Consecutive parameters of the same type can share it.

go
1
2
3
func add(a, b float32) float32 {
	return a + b
}

A Go function can return several values. This is used everywhere, most importantly to return a result together with an error.

go
1
2
3
4
5
6
func getResults(a, b int) (int, int) {
	return a + b, a * b
}

sum, product := getResults(2, 3)
fmt.Printf("sum=%d product=%d\n", sum, product) // sum=5 product=6

If you only need one of the values, discard the other with the blank identifier _:

go
1
sum, _ := getResults(2, 3)

Reading user input

The original example reads a name and two numbers from the terminal with fmt.Scanln. It writes into the variables through their addresses, which is why you pass &name (we cover pointers properly in the pointers chapter).

input.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func getTwoNumbers() (int, int) {
	var a, b int
	fmt.Print("Enter two numbers separated by a space: ")
	if _, err := fmt.Scanln(&a, &b); err != nil {
		fmt.Println("invalid input:", err)
	}
	return a, b
}

func main() {
	var name string
	fmt.Print("Enter your name: ")
	fmt.Scanln(&name)

	a, b := getTwoNumbers()
	fmt.Printf("Hello %s, %d + %d = %d\n", name, a, b, a+b)
}

Notice how each function does one small job (read name, read numbers, print). Small, single-purpose functions are idiomatic Go.

Errors are values

Go has no exceptions for normal failures. Functions return an error as their last value and callers check it immediately:

go
1
2
3
4
5
6
value, err := strconv.Atoi("12a")
if err != nil {
	fmt.Println("could not parse:", err)
	return
}
fmt.Println(value)

This looks repetitive at first, but it makes every failure path visible right where it happens, which is exactly what you want in production code.

Key takeaways

Every variable has a zero value, so there is no "undefined".
:= declares and assigns inside functions; var works everywhere.
switch cases do not fall through, and for is the only loop.
Functions can return multiple values; the last one is usually an error.
Check errors right where they happen: if err != nil { ... }.
#Golang#Go basics#Beginner

Last updated on

Discussion0