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.
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.
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.
go mod init example.com/hello # once per project: creates go.mod
go run . # compile and run
go build -o hello . # produce a binarygofmt -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.
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.
Control flow: if and switch
if needs no parentheses, but braces are mandatory.
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:
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.
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:
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:
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.
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.
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=6If you only need one of the values, discard the other with the blank identifier _:
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).
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:
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
:= declares and assigns inside functions; var works everywhere.switch cases do not fall through, and for is the only loop.error.if err != nil { ... }.Last updated on