Day 02: Variables, Types, and Control Flow
Core Concepts
Declaration Styles
Go has two ways to declare variables:
var x int = 10 // explicit type and value
var y = 20 // type inferred from value
z := 30 // short declaration, inside functions only
var works at package scope; := only works inside functions. Prefer := locally; use var for zero-value declarations (var count int).
Numeric Types and Conversion
All numeric conversions in Go are explicit. There is no implicit widening.
var i int = 42
var f float64 = float64(i) // must convert
var u uint = uint(f)
Integer division truncates: 7 / 2 == 3. For float division, at least one operand must be float64.
Constants
const Pi = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
)
Constants are typed or untyped. Untyped constants adapt to context:
const Big = 1 << 62 // fits in int64 but not int32
Control Flow
Go has one loop keyword — for — used for all looping patterns:
for i := 0; i < 10; i++ { } // C-style
for condition { } // while-style
for { } // infinite loop
for i, v := range slice { } // range loop
switch in Go does not fall through by default (no break needed):
switch op {
case '+': fmt.Println(a + b)
case '-': fmt.Println(a - b)
case '*': fmt.Println(a * b)
case '/':
if b == 0 {
fmt.Println("error: division by zero")
} else {
fmt.Printf("%.2f\n", a/b)
}
default: fmt.Println("unknown operator")
}
if can have an init statement:
if err := doSomething(); err != nil {
// handle error
}
Zero Values
Every variable in Go is initialised to its zero value if not assigned:
| Type | Zero value |
|---|---|
int, float64 |
0 |
bool |
false |
string |
"" |
| pointer, slice, map | nil |
Labs
Lab 1: All Basic Types and Their Zero Values
What you’ll practise: Declaring every basic Go type and observing zero values when a variable is declared but not assigned.
Task: Write a program that declares one variable of each basic type without assigning a value, prints the zero value, then assigns and prints a real value.
Steps:
- Declare
varfor each type:bool,int,int8,int32,int64,uint,float32,float64,complex128,byte,rune,string - Print each zero value with its type using
%T %v - Assign real values and print again
package main
import "fmt"
func main() {
var b bool
var i int
var f32 float32
var f64 float64
var c complex128
var by byte // alias for uint8
var r rune // alias for int32
var s string
fmt.Printf("bool: %T = %v\n", b, b)
fmt.Printf("int: %T = %v\n", i, i)
fmt.Printf("float32: %T = %v\n", f32, f32)
fmt.Printf("float64: %T = %v\n", f64, f64)
fmt.Printf("complex128: %T = %v\n", c, c)
fmt.Printf("byte: %T = %v\n", by, by)
fmt.Printf("rune: %T = %v\n", r, r)
fmt.Printf("string: %T = %q\n", s, s)
}
Expected output:
bool: bool = false
int: int = 0
float32: float32 = 0
float64: float64 = 0
complex128: complex128 = (0+0i)
byte: uint8 = 0
rune: int32 = 0
string: string = ""
Checkpoint: Every type prints its zero value. Notice byte prints as uint8 and rune as int32 — they are aliases, not distinct types.
Lab 2: Type Conversion Experiment
What you’ll practise: Explicit numeric conversion, truncation rules, and the difference between converting an int to string via rune vs strconv.Itoa.
Task: Convert between numeric types and strings, observing what gets lost and why.
Steps:
- Convert
inttofloat64and back — observe truncation - Divide two ints and observe integer division; then force float division
- Convert an
inttostringtwo ways:string(rune(n))andstrconv.Itoa(n)— compare the results
package main
import (
"fmt"
"strconv"
)
func main() {
i := 7
f := float64(i)
fmt.Printf("int→float64: %v\n", f) // 7
price := 3.99 // float64 variable — conversion of a non-constant is allowed
back := int(price)
fmt.Printf("float→int (truncates): %v\n", back) // 3
// Integer division vs float division
fmt.Printf("7 / 2 = %v (int division)\n", 7/2)
fmt.Printf("7.0 / 2.0 = %v (float division)\n", 7.0/2.0)
// int to string: two very different results
n := 65
fmt.Printf("string(rune(%d)) = %q\n", n, string(rune(n))) // "A" (Unicode code point)
fmt.Printf("strconv.Itoa(%d) = %q\n", n, strconv.Itoa(n)) // "65" (decimal digits)
}
Expected output:
int→float64: 7
float→int (truncates): 3
7 / 2 = 3 (int division)
7.0 / 2.0 = 3.5 (float division)
string(rune(65)) = "A"
strconv.Itoa(65) = "65"
Checkpoint: You can explain why string(rune(65)) produces "A" (it treats 65 as a Unicode code point) while strconv.Itoa(65) produces "65" (it converts the number to decimal text).
Lab 3: All Forms of the for Loop
What you’ll practise: Writing C-style, while-equivalent, infinite-with-break, and range-over-string loops.
Task:
Count vowels in a sentence using a range loop over the string, and also demonstrate each other loop form.
Steps:
- Write a C-style
forthat prints numbers 1–5 - Write a while-equivalent
for condition {}that halves a number until it’s below 1 - Write an infinite loop with
breakthat reads the first 3 even numbers from a counter - Use
rangeover a string to count vowels (handle multi-byte runes correctly)
package main
import (
"fmt"
"strings"
)
func main() {
// C-style
for i := 1; i <= 5; i++ {
fmt.Printf("%d ", i)
}
fmt.Println()
// while-equivalent
n := 64.0
for n >= 1 {
n /= 2
}
fmt.Printf("halved to: %.4f\n", n)
// infinite loop with break
count, val := 0, 0
for {
val++
if val%2 == 0 {
fmt.Printf("even: %d\n", val)
count++
}
if count == 3 {
break
}
}
// range over string — counts vowels
sentence := "The quick brown fox jumps over the lazy dog"
vowels := 0
for _, r := range sentence {
if strings.ContainsRune("aeiouAEIOU", r) {
vowels++
}
}
fmt.Printf("Vowels in sentence: %d\n", vowels)
}
Expected output:
1 2 3 4 5
halved to: 0.5000
even: 2
even: 4
even: 6
Vowels in sentence: 11
Checkpoint: All four loop forms run without error. Change the sentence and verify the vowel count updates correctly.
Lab 4: switch — Grade Calculator
What you’ll practise: Expression switch with no condition (acts like an if/else chain) and a value switch on a string.
Task: Write a grade calculator that maps a score to a letter grade, then add a second switch that translates the letter grade to a descriptive label.
Steps:
- Write
grade(score int) stringusingswitchwith no condition - Write
label(grade string) stringusing a value switch on a string - Call both for a range of scores
package main
import "fmt"
func grade(score int) string {
switch {
case score >= 90:
return "A"
case score >= 80:
return "B"
case score >= 70:
return "C"
case score >= 60:
return "D"
default:
return "F"
}
}
func label(g string) string {
switch g {
case "A":
return "Excellent"
case "B":
return "Good"
case "C":
return "Satisfactory"
case "D":
return "Passing"
default:
return "Failing"
}
}
func main() {
scores := []int{95, 82, 73, 60, 45}
for _, score := range scores {
g := grade(score)
fmt.Printf("Score %d → %s (%s)\n", score, g, label(g))
}
}
Expected output:
Score 95 → A (Excellent)
Score 82 → B (Good)
Score 73 → C (Satisfactory)
Score 60 → D (Passing)
Score 45 → F (Failing)
Checkpoint: Add scores 90 and 70 to the slice — confirm they map to A and C respectively (boundary values are inclusive).
Lab 5: const and iota — Weekdays and Directions
What you’ll practise: Defining typed constants with iota, using iota with arithmetic expressions, and printing named constants.
What is
iota?
iotais a built-in constant generator that resets to0at the start of eachconstblock and increments by1for each successive constant spec. It lets you define a sequence of related constants without repeating yourself.const ( A = iota // 0 B = iota // 1 C = iota // 2 )Because the expression repeats implicitly, this is identical to:
const ( A = iota // 0 B // 1 C // 2 )You can use
iotainside any expression, which is where it gets powerful:
Expression Values produced iota0, 1, 2, 3, … iota + 11, 2, 3, 4, … iota * 100, 10, 20, 30, … 1 << iota1, 2, 4, 8, … (bit flags)
iotaresets to0at the start of each newconstblock, so two separateconst (...)blocks each start from0.Official documentation: Go Spec — iota
Task:
Define a Weekday type with iota starting at 1 (Monday=1), and a Direction type with bitfield constants. Print their values and names.
Steps:
- Define
type Weekday intwithMondaythroughSundayusingiota + 1 - Add a
String() stringmethod onWeekdaythat returns the day name - Define
type Direction uintwithN,S,E,Was powers of 2 using1 << iota - Print all weekdays and directions
package main
import "fmt"
type Weekday int
const (
Monday Weekday = iota + 1
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
func (d Weekday) String() string {
names := [...]string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
if d < Monday || d > Sunday {
return fmt.Sprintf("Unknown(%d)", int(d))
}
return names[d-1]
}
type Direction uint
const (
North Direction = 1 << iota // 1
South // 2
East // 4
West // 8
)
func main() {
for d := Monday; d <= Sunday; d++ {
fmt.Printf("%d: %s\n", d, d)
}
fmt.Println()
for _, dir := range []Direction{North, South, East, West} {
fmt.Printf("Direction value: %d\n", dir)
}
}
Expected output:
1: Monday
2: Tuesday
3: Wednesday
4: Thursday
5: Friday
6: Saturday
7: Sunday
Direction value: 1
Direction value: 2
Direction value: 4
Direction value: 8
Checkpoint: Weekday(8).String() returns "Unknown(8)". Direction values are powers of 2 — they can be combined with bitwise OR for flag-style usage.
Lab 6 (Final): CLI Calculator
What you’ll practise: Combining variables, all numeric types, switch, and os.Args into a fully functional command-line tool.
Task:
Write day-02/main.go — a CLI calculator that reads two numbers and an operator from os.Args, uses switch to compute the result, and guards against division by zero.
Steps:
- Read
os.Args[1],os.Args[2],os.Args[3]for the two operands and operator - Parse the operands to
float64usingstrconv.ParseFloat - Use
switchon the operator string to compute+,-,*,/ - Guard division by zero in the
/case - Print a formatted result line
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
if len(os.Args) != 4 {
fmt.Fprintf(os.Stderr, "usage: calc <num> <op> <num>\n")
os.Exit(1)
}
a, err := strconv.ParseFloat(os.Args[1], 64)
if err != nil {
fmt.Fprintf(os.Stderr, "invalid number: %s\n", os.Args[1])
os.Exit(1)
}
b, err := strconv.ParseFloat(os.Args[3], 64)
if err != nil {
fmt.Fprintf(os.Stderr, "invalid number: %s\n", os.Args[3])
os.Exit(1)
}
op := os.Args[2]
switch op {
case "+":
fmt.Printf("%.2f + %.2f = %.2f\n", a, b, a+b)
case "-":
fmt.Printf("%.2f - %.2f = %.2f\n", a, b, a-b)
case "*":
fmt.Printf("%.2f * %.2f = %.2f\n", a, b, a*b)
case "/":
if b == 0 {
fmt.Fprintln(os.Stderr, "error: division by zero")
os.Exit(1)
}
fmt.Printf("%.2f / %.2f = %.2f\n", a, b, a/b)
default:
fmt.Fprintf(os.Stderr, "unknown operator: %s\n", op)
os.Exit(1)
}
}
Expected output:
$ go run . 12 / 4
12.00 / 4.00 = 3.00
$ go run . 7 + 3
7.00 + 3.00 = 10.00
$ go run . 10 / 0
error: division by zero
Checkpoint: All four operators work. Division by zero exits with a non-zero status (echo $? prints 1). Passing a non-numeric argument prints a helpful error.
Day Project: CLI Calculator
Declare two float64 values and a byte operator. Use switch to compute the result. Guard division by zero.
a, b := 12.0, 4.0
op := byte('/')
switch op {
case '/':
if b == 0 { fmt.Println("error: division by zero"); return }
fmt.Printf("%.2f / %.2f = %.2f\n", a, b, a/b)
// ...
}
Extension ideas: read a, b, and op from os.Args; add % modulo; use fmt.Sscanf to parse a string like "12.0 / 4.0".
Official Documentation
fmt— formatted I/O (Printf, Println, Sscanf)os—os.Argsfor command-line arguments- Language Spec: Variables — variable declarations
- Language Spec: Constants — typed and untyped constants
- Language Spec: For statements — all loop forms
- Language Spec: Switch statements — expression and type switches
- Go Tour: Flow control — interactive control flow tour