8.5 KiB
Go Basics, Part 1 — Syntax, Types, and Control Flow
This is the first of three "Go Basics" lessons. If you've never written Go before, do all three before starting Lesson 1 of the main course. If you already know another programming language (Python, JavaScript, PHP, Java, C, etc.), you'll move through this fast — Go is a small, simple language on purpose.
1. Installing Go and running your first program
Download and install Go from go.dev/dl. Confirm it worked:
go version
You should see something like go version go1.26.x.
Make a folder and your first program:
mkdir hello && cd hello
go mod init hello
go mod init hello creates a go.mod file — this marks the folder as a
Go module (a self-contained project with its own dependencies). We'll
explain modules properly in Part 3; for now, just know every Go project
needs one.
main.go
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
Run it:
go run .
You should see hello, world printed. Let's break down every single piece
of that file, since you'll type this pattern constantly:
package main— every Go file starts by declaring which package it belongs to. A package is just a folder of.gofiles that are compiled together and can freely call each other's code.package mainis special: it means "this produces a runnable program," not a reusable library.import "fmt"— pulls in the standard library'sfmtpackage (short for "format"), which has functions for printing text and formatting strings.func main()— the function namedmain, insidepackage main, is the entry point. When you run the compiled program, execution starts here. There must be exactly onemainfunction inpackage main.fmt.Println("hello, world")— calls thePrintlnfunction from thefmtpackage (note the dot:package.Function), passing it the string"hello, world".Printlnprints its arguments followed by a newline.
Two ways to run Go code:
go run .(orgo run main.go) — compiles and runs immediately, doesn't leave a binary behind. Good for development.go build .— compiles into an actual executable file (e.g.helloorhello.exe) that you can run directly (./hello) without Go installed on the machine that runs it. This is what you'd do to ship the program.
2. Variables and basic types
Go is statically typed: every variable has a fixed type, decided either explicitly or by inference, and that type never changes.
package main
import "fmt"
func main() {
// Explicit type
var age int = 30
// Type inferred from the value (int, since 30 has no decimal point)
var name = "Hamid"
// The short declaration operator ":=" — declares AND assigns in one
// step, inferring the type. This is by far the most common way to
// declare variables inside a function body.
city := "Tehran"
height := 1.78 // inferred as float64
fmt.Println(age, name, city, height)
// Reassignment - no "var" or ":=" needed, the variable already exists
age = 31
fmt.Println(age)
// Multiple variables at once
var x, y int = 1, 2
a, b := 3, 4
fmt.Println(x, y, a, b)
}
Key rules:
:=can ONLY be used to declare a new variable (usually inside a function). It's shorthand forvar x = valuewith the type inferred.varcan be used with or without an initial value:var count intdeclarescountas anintwith the zero value0(Go always initializes variables — there's no "undefined" or garbage value).- You cannot declare a variable and never use it — Go's compiler will refuse to build code with an unused local variable. This trips up everyone coming from other languages at first.
The built-in types you'll use constantly
| Type | Example | Notes |
|---|---|---|
int |
42 |
Platform-dependent size (64-bit on modern machines); use this by default for whole numbers |
string |
"hello" |
UTF-8 text, immutable |
bool |
true, false |
|
float64 |
3.14 |
Default type for decimal numbers |
byte |
Alias for uint8, used for raw binary data |
|
[]byte |
A "slice of bytes" — how Go represents raw binary data (we cast strings to this constantly, e.g. for password hashing) |
Zero values (what a variable holds if declared without an initial value):
int → 0, string → "" (empty string), bool → false, pointers →
nil (explained in Part 2).
Type conversion
Go never silently converts between types (unlike JavaScript or PHP). You must convert explicitly:
var i int = 42
var f float64 = float64(i) // must explicitly convert int -> float64
var s string = fmt.Sprintf("%d", i) // int -> string via formatting
id := "123"
// s, err := strconv.Atoi(id) // string -> int, using the strconv package
You'll see this constantly in the main course, e.g. int(id) when
converting a database's int64 auto-increment ID into our own int
field.
3. if, for, and switch
Go has exactly one looping construct — for — no while, no do-while.
It also does not use parentheses around conditions, but curly braces
{ } are always required (even for a single-line body).
if
age := 20
if age >= 18 {
fmt.Println("adult")
} else if age >= 13 {
fmt.Println("teenager")
} else {
fmt.Println("child")
}
A very common Go idiom: declaring a variable that's scoped ONLY to the
if/else block, often used with error handling (you'll see this
constantly starting in Part 2):
if err := doSomething(); err != nil {
fmt.Println("failed:", err)
}
// err doesn't exist out here — it was scoped to the if statement
for — Go's only loop
// Classic three-part loop (like C's for)
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// "while" style - just the condition
count := 0
for count < 3 {
fmt.Println("counting:", count)
count++
}
// Infinite loop - break out manually
for {
fmt.Println("runs forever until break")
break
}
// Looping over a collection (slices, maps - covered in Part 3)
names := []string{"alice", "bob", "carol"}
for index, name := range names {
fmt.Println(index, name)
}
// If you don't need the index, use _ (the "blank identifier") to
// explicitly discard it - Go's compiler complains about unused
// variables, and _ is the escape hatch:
for _, name := range names {
fmt.Println(name)
}
You'll see _ constantly throughout the main course — any time a
function returns something you genuinely don't need, _ discards it
without triggering an "unused variable" error.
switch
day := "Monday"
switch day {
case "Saturday", "Sunday":
fmt.Println("weekend")
default:
fmt.Println("weekday")
}
Unlike C/Java, Go's switch cases do not fall through by default —
each case automatically breaks after its block, no break statement
needed.
4. Strings, formatting, and comments
name := "Hamid"
age := 31
// Println - space-separated, newline at the end
fmt.Println("name:", name, "age:", age)
// Printf - C-style format string, YOU add the newline with \n
fmt.Printf("name: %s, age: %d\n", name, age)
// Sprintf - same as Printf but returns a string instead of printing it
message := fmt.Sprintf("hello %s, you are %d years old", name, age)
fmt.Println(message)
Common format verbs you'll use throughout the course:
| Verb | Meaning |
|---|---|
%s |
string |
%d |
integer |
%f |
float |
%v |
"default" representation of any value — great for debugging |
%+v |
like %v but includes struct field names |
%w |
wraps an error (covered in Part 3) — only valid with fmt.Errorf |
Comments:
// A single-line comment
/*
A multi-line comment.
*/
// A comment directly above a function/type, with no blank line between,
// is a DOC comment - tools display it as that function's documentation.
// This project's code uses these constantly.
func DoSomething() {}
5. Try it yourself
Before moving to Part 2, write a small standalone program (new folder,
go mod init practice, main.go) that:
- Declares a
stringname and anintage using:=. - Uses
if/else if/elseto print a different message depending on the age (e.g. "minor", "adult", "senior" for under 18 / under 65 / 65+). - Uses a
forloop to print the numbers 1 through 10. - Uses
fmt.Printfto print the name and age formatted into one sentence.
Run it with go run .. Once this feels natural, move to Part 2 —
functions, structs, and pointers, which is where Go starts looking like
the code you'll write in the actual course.