Exercises — Module 1: Types and Variables¶
Work through exercises in order — they're designed to build on each other. Attempt each problem genuinely before looking at the solution. Seeing the solution first might feel like progress, but it isn't.
Instructions¶
- Attempt first. Spend at least the estimated time on each problem before checking hints or solutions.
- Write your work. Don't just read through — actually type and run your code.
- Check your answer against the acceptance criteria, not just the solution code.
- Score yourself honestly in the Scoring Log at the bottom.
- If you're stuck after a genuine effort, use the hints one at a time — not all at once.
Difficulty Legend¶
| Symbol | Difficulty | Expected Time | Points |
|---|---|---|---|
| 🟢 Easy | Recall and basic application | 5–10 min | 1 pt |
| 🟡 Medium | Requires combining 2+ concepts | 15–25 min | 2 pts |
| 🔴 Hard | Multi-step, requires real problem-solving | 30–60 min | 3 pts |
| ⭐ Challenge | Open-ended; more than one good answer | 60+ min | 5 pts |
Exercise 1: Zero Value Survey [🟢 Easy] [1 pt]¶
Context¶
Before you can work with Go types confidently, you need to know exactly what each type looks like when it is not explicitly initialized. This exercise forces you to observe zero values directly — and to predict them before running, which is the real test.
Task¶
Declare variables of 5 different types (int, float64, string, bool, and one of your choice from: int64, uint8, rune, complex128) using var with an explicit type but no initial value. Print each variable. Before running the code, write down your prediction for each zero value.
Requirements¶
- Use
var name typesyntax (no=or:=) - Declare at least 5 different types
- Print each variable with a label (e.g.,
fmt.Printf("int: %v\n", i)) - Use
%qfor the string so the empty string is visible as""
Hints¶
Hint 1 (try without this first)
`var x int` declares `x` as an int with no explicit value. Go will initialize it to the zero value for `int`.Hint 2 (only if Hint 1 wasn't enough)
The `%q` verb in `fmt.Printf` prints strings with surrounding quotes — useful for making an empty string visible: `fmt.Printf("string: %q\n", s)` prints `string: ""`.Expected Output / Acceptance Criteria¶
Solution¶
Show Solution (attempt first!)
**Explanation:** Every type in Go has a defined zero value. For numeric types it is `0`, for `bool` it is `false`, for `string` it is `""` (empty string), and for `rune` (which is `int32`) it is also `0`. The `%q` verb for strings makes the empty string visible as `""` rather than just a blank line. **Common wrong answers and why they fail:** - Declaring `var s string = ""` — this works but misses the point; the exercise is about observing that you don't need to write `= ""` because Go does it automatically. - Using `:=` — this requires a value on the right side; you cannot write `s := ` with nothing after it.Exercise 2: Type Inference Inspector [🟢 Easy] [1 pt]¶
Context¶
When you use :=, Go infers the type from the value. This exercise gives you practice observing what types Go actually infers, which is important for avoiding surprises when you later need a specific type.
Task¶
Use the short declaration := to declare 3 variables with different literal values. Print both the value and the type of each variable using fmt.Printf("%T\n", x). Include at least one integer literal, one floating-point literal, and one string literal.
Requirements¶
- Use
:=for all declarations - Declare at least 3 variables with different literal types
- Print each variable's type using
%T - Write down your prediction before running: what type will each literal produce?
Hints¶
Hint 1
`fmt.Printf("%T\n", x)` prints the type of `x`. For example, if `x := 42`, it prints `int`. The format verb `%T` (capital T) is the type verb.Expected Output / Acceptance Criteria¶
(Exact values will differ based on your chosen literals.)Solution¶
Show Solution
**Explanation:** Integer literals default to `int`, floating-point literals default to `float64`, and string literals default to `string`. These defaults apply whenever you use `:=` or `var name = value`. If you need a different type — say `int32` or `float32` — you must force it with an explicit conversion: `a := int32(42)`.Exercise 3: Days of the Week with iota [🟢 Easy] [1 pt]¶
Context¶
iota is Go's mechanism for defining enumerated constants without boilerplate. This exercise builds the muscle memory for the basic iota pattern that appears in nearly every real Go codebase.
Task¶
Create a const block using iota to represent the days of the week, with Sunday = 0 through Saturday = 6. In main, print the numeric value of each day constant.
Requirements¶
- Use a
constblock withiota -
Sundaymust equal0,Saturdaymust equal6 - Print each constant's value in
main - Confirm that
iotaproduces the expected integer values
Hints¶
Hint 1
In a `const` block, you only need to write `= iota` on the first constant. Subsequent constants in the same block automatically use the same expression with an incremented `iota` value.Hint 2
Each line below `Sunday` implicitly repeats the `= iota` expression with the next value.Expected Output / Acceptance Criteria¶
Solution¶
Show Solution (attempt first!)
package main
import "fmt"
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func main() {
fmt.Printf("Sunday: %d\n", Sunday)
fmt.Printf("Monday: %d\n", Monday)
fmt.Printf("Tuesday: %d\n", Tuesday)
fmt.Printf("Wednesday: %d\n", Wednesday)
fmt.Printf("Thursday: %d\n", Thursday)
fmt.Printf("Friday: %d\n", Friday)
fmt.Printf("Saturday: %d\n", Saturday)
}
Exercise 4: Temperature Converter [🟡 Medium] [2 pts]¶
Context¶
Floating-point arithmetic and type-safe variables are used constantly in scientific, financial, and engineering code. This exercise requires you to think carefully about float64 vs integer arithmetic — a subtle but important distinction.
Task¶
Write a temperature converter that converts Celsius to Fahrenheit for a set of hardcoded input values. Use the formula F = C * 9/5 + 32. Test with at least these three values: 0°C, 100°C, and -40°C.
Requirements¶
- Declare all temperature variables as
float64 - Use the formula
F = C * 9.0/5.0 + 32.0(with.0suffixes — explain in a comment why) - Print both values with exactly 2 decimal places using
fmt.Printf("%.2f°C = %.2f°F\n", c, f) - Verify: 0°C = 32°F, 100°C = 212°F, -40°C = -40°F
Hints¶
Hint 1
In Go, `9/5` is integer division and evaluates to `1` (not `1.8`). This would make your formula `F = C * 1 + 32`, which gives completely wrong results. Using `9.0/5.0` or `float64(9)/float64(5)` forces floating-point division.Hint 2
The `%.2f` format verb prints a float with exactly 2 decimal places. `%f` prints 6 decimal places by default.Expected Output / Acceptance Criteria¶
Solution¶
Show Solution
**Explanation:** The critical detail is `9.0/5.0` instead of `9/5`. In Go, if both operands of `/` are untyped integer literals, the result is integer division. `9/5 = 1`, not `1.8`. Using `9.0` makes both operands floating-point literals, so the division produces `1.8`. Since `celsius` is `float64`, the full expression `celsius*9.0/5.0+32.0` is a `float64` computation throughout. **Alternative approaches:** You could also write `celsius*(9.0/5.0) + 32.0` or even precompute `const factor = 9.0 / 5.0` and use `celsius*factor + 32.0`. The key constraint is ensuring no integer division occurs anywhere in the formula.Exercise 5: Permission Flags with iota [🟡 Medium] [2 pts]¶
Context¶
Bit-flag constants using iota with bit shifts appear in Go's standard library (e.g., os.FileMode, HTTP method flags, log level flags) and in nearly every systems-level Go package. This exercise builds familiarity with the pattern so you can read and write it fluently.
Task¶
Define file permission constants using iota with bit shifts: ReadPerm = 1, WritePerm = 2, ExecPerm = 4. Then write a function printPerms(p int) that prints which permissions are set. In main, test at least three permission combinations.
Requirements¶
- Define
ReadPerm,WritePerm, andExecPermusing1 << iota - Write a function that tests each flag using bitwise AND (
&) - Test combinations: read-only, read+write, and all three
- Print the numeric value of each permission set alongside the flag names
Hints¶
Hint 1
To test whether a flag bit is set, use `perms & FlagName != 0`. The bitwise AND isolates the bit: if that bit is 1 in both operands, the result is non-zero; otherwise it is 0.Hint 2
To combine multiple flags, use bitwise OR (`|`): `ReadPerm | WritePerm` gives a value with both the read and write bits set.Hint 3 (near-solution hint — only if truly stuck)
Each successive constant doubles the value because bit position increases by 1.Expected Output / Acceptance Criteria¶
Solution¶
Show Solution
package main
import "fmt"
const (
ReadPerm = 1 << iota // 1
WritePerm // 2
ExecPerm // 4
)
func printPerms(p int) {
fmt.Printf("Permissions (%d):", p)
if p&ReadPerm != 0 {
fmt.Print(" read")
}
if p&WritePerm != 0 {
fmt.Print(" write")
}
if p&ExecPerm != 0 {
fmt.Print(" execute")
}
fmt.Println()
}
func main() {
printPerms(ReadPerm) // 1
printPerms(ReadPerm | WritePerm) // 3
printPerms(ReadPerm | WritePerm | ExecPerm) // 7
}
Exercise 6: Type Conversion Roundtrip [🟡 Medium] [2 pts]¶
Context¶
Type conversion in Go is explicit — and for good reason. This exercise forces you to observe what actually happens during numeric conversions, particularly the precision loss from float-to-int, and demonstrates the string(int) vs strconv.Itoa(int) distinction that trips up many beginners.
Task¶
Write a program that: (1) starts with an int value of 42, converts it to float64, adds 0.9, then converts back to int — and prints what is lost; (2) demonstrates the difference between string(65) and strconv.Itoa(65).
Requirements¶
- Show the float64 value after adding 0.9: should be 42.9
- Show the int value after converting back: should be 42 (not 43 — float-to-int truncates)
- Print
string(65)and explain in a comment what it produces - Print
strconv.Itoa(65)and explain in a comment what it produces - Import
strconv
Hints¶
Hint 1
`int(3.9)` truncates toward zero in Go — it gives `3`, not `4`. This is different from rounding. `int(-3.9)` gives `-3`, not `-4`.Hint 2
`string(n)` where `n` is an integer converts the integer as a Unicode code point. Code point 65 is 'A'. To get the decimal string `"65"`, use `strconv.Itoa(65)` (or `fmt.Sprintf("%d", 65)`).Expected Output / Acceptance Criteria¶
Start: 42 (int)
After float64: 42.9
After int: 42 (truncated, not rounded — 0.9 is lost)
string(65): A (Unicode code point 65 = 'A')
Itoa(65): 65 (decimal string representation)
Solution¶
Show Solution
package main
import (
"fmt"
"strconv"
)
func main() {
// Part 1: Numeric type conversion roundtrip
original := 42
asFloat := float64(original) + 0.9
backToInt := int(asFloat) // truncates toward zero — does NOT round
fmt.Printf("Start: %d (int)\n", original)
fmt.Printf("After float64: %.1f\n", asFloat)
fmt.Printf("After int: %d (truncated, not rounded — 0.9 is lost)\n", backToInt)
fmt.Println()
// Part 2: string(int) vs strconv.Itoa(int)
n := 65
asChar := string(n) // Unicode code point 65 = 'A'
asDecimal := strconv.Itoa(n) // decimal string representation = "65"
fmt.Printf("string(65): %s (Unicode code point 65 = 'A')\n", asChar)
fmt.Printf("Itoa(65): %s (decimal string representation)\n", asDecimal)
}
Exercise 7: Integer Overflow Demonstration [🔴 Hard] [3 pts]¶
Context¶
Go's integer overflow behavior is silent — no panic, no error, just wrapping arithmetic. Understanding this is essential for any code that does numeric computation, especially with fixed-size types. This exercise asks you not just to reproduce the behavior but to explain it at the bit level.
Task¶
Write a program that demonstrates integer overflow for int8. Declare var x int8 = 127 (the maximum value for int8), increment it with x++, and print the result. Do the same for uint8 = 255. Print clear before/after labels. Add a comment explaining why the value wraps to what it does.
Requirements¶
- Demonstrate
int8overflow from127to-128 - Demonstrate
uint8overflow from255to0 - Print before and after values with labels
- Include a comment explaining the two's-complement reason for
int8wrapping to-128 - Include a comment noting that Go does NOT panic on overflow
Hints¶
Hint 1 (structural hint — try designing without this first)
`var x int8 = 127` declares a signed 8-bit integer at its maximum value. `x++` is equivalent to `x = x + 1`. The compiler will not prevent this.Hint 2 (conceptual hint)
`int8` uses 8 bits with two's-complement representation. The bit pattern `01111111` is `127`. Adding 1 produces `10000000`. In two's-complement 8-bit encoding, `10000000` represents `-128`.Hint 3 (near-solution hint — only if truly stuck)
For `uint8`: all 8 bits set (`11111111`) is `255`. Adding 1 produces `100000000`, which is 9 bits — but `uint8` only has 8 bits, so the top bit is dropped, leaving `00000000` = `0`.Expected Output / Acceptance Criteria¶
Solution¶
Show Solution
package main
import "fmt"
func main() {
// int8 overflow: max value is 127 (binary: 01111111)
// Adding 1 produces binary: 10000000
// In two's-complement 8-bit, 10000000 represents -128
// Go does NOT panic — it silently wraps around
var x int8 = 127
fmt.Println("int8 overflow:")
fmt.Printf(" Before: %d\n", x)
x++
fmt.Printf(" After: %d\n", x) // -128
fmt.Println()
// uint8 overflow: max value is 255 (binary: 11111111)
// Adding 1 produces binary: 100000000 (9 bits)
// uint8 only holds 8 bits, so the carry bit is discarded
// Result is 00000000 = 0
var u uint8 = 255
fmt.Println("uint8 overflow:")
fmt.Printf(" Before: %d\n", u)
u++
fmt.Printf(" After: %d\n", u) // 0
}
Exercise 8: Swap Without a Temp Variable [🔴 Hard] [3 pts]¶
Context¶
Go's multiple assignment feature — where the right-hand side is evaluated completely before any assignment happens — enables a clean swap idiom that requires no temporary variable. This exercise solidifies your understanding of how Go's simultaneous assignment works.
Task¶
Write a program with two integer variables a and b. Without declaring any intermediate "temp" variable, swap the values of a and b using Go's multiple assignment. Print the values before and after the swap. Then explain in a comment why a, b = b, a works correctly but sequential a = b; b = a does not.
Requirements¶
- Declare
aandbwith distinct initial values - Swap without a temp variable using
a, b = b, a - Print before and after values
- Include a comment explaining why sequential assignment fails but simultaneous assignment works
- Add a third variable
cand demonstrate a three-way rotation:a, b, c = b, c, a
Hints¶
Hint 1
In Go's multiple assignment (`a, b = b, a`), all right-hand side expressions are evaluated before any assignment is performed. Both `b` and `a` are read with their original values, then the assignments happen.Hint 2
Sequential assignment (`a = b; b = a`) overwrites `a` before `b` is assigned from the old `a`. After `a = b`, the original value of `a` is gone, so `b = a` just copies `b` back to itself.Expected Output / Acceptance Criteria¶
Before swap: a=10 b=20
After swap: a=20 b=10
Before rotation: a=1 b=2 c=3
After rotation: a=2 b=3 c=1
Solution¶
Show Solution
package main
import "fmt"
func main() {
a, b := 10, 20
fmt.Printf("Before swap: a=%d b=%d\n", a, b)
// Go evaluates the entire right side before any assignment.
// b and a are read with original values, THEN assigned.
// Sequential would fail: a=b destroys original a before b=a can use it.
a, b = b, a
fmt.Printf("After swap: a=%d b=%d\n", a, b)
fmt.Println()
// Three-way rotation using simultaneous assignment
a, b, c := 1, 2, 3
fmt.Printf("Before rotation: a=%d b=%d c=%d\n", a, b, c)
// All three right-hand values (b, c, a) are read first,
// then assigned to (a, b, c) simultaneously.
a, b, c = b, c, a
fmt.Printf("After rotation: a=%d b=%d c=%d\n", a, b, c)
}
Exercise 9: Custom Enum Type with String() [⭐ Challenge] [5 pts]¶
Context¶
Go has no built-in enum keyword. Instead, the idiomatic pattern is to define a named integer type and use iota for its values. When combined with the fmt.Stringer interface (a type with a String() string method), the enum becomes human-readable automatically. This exercise previews interfaces but is grounded in the types-and-variables concepts from this module.
Task¶
Create a custom type Weekday based on int. Define Sunday through Saturday as constants of that type using iota. Implement a String() string method on Weekday that returns the human-readable name. Use a switch statement inside String(). In main, create a few Weekday values and print them — they should print as names, not numbers.
Requirements¶
- Define
type Weekday int - Define constants
SundaythroughSaturdayof typeWeekdayusingiota - Implement
func (d Weekday) String() stringusing a switch statement - Return
"Unknown"from the default case - In
main, print at least threeWeekdayvalues usingfmt.Println— they should print as day names - Demonstrate that the
Weekdaytype prevents accidental misuse: show thatvar d Weekday = 8compiles but that this value triggers the"Unknown"case inString()
Hints¶
Hint 1 (structural hint)
A method with signature `func (d Weekday) String() string` satisfies the `fmt.Stringer` interface. When `fmt.Println` receives a value that implements `Stringer`, it calls `String()` automatically instead of printing the raw integer.Hint 2 (conceptual hint)
Notice `Sunday Weekday = iota` — not just `Sunday = iota`. This gives the constants the type `Weekday`, not just `int`.Hint 3 (near-solution hint)
Expected Output / Acceptance Criteria¶
(The values print as day names, not numbers, becausefmt.Println calls String() automatically.)
Solution¶
Show Solution
package main
import "fmt"
// Weekday is a named type based on int.
// This gives the type its own identity — Weekday and int are distinct types,
// even though Weekday's underlying type is int.
type Weekday int
const (
Sunday Weekday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
// String implements the fmt.Stringer interface.
// When fmt.Println (or any fmt function) encounters a Weekday value,
// it calls String() automatically to get the human-readable representation.
func (d Weekday) String() string {
switch d {
case Sunday:
return "Sunday"
case Monday:
return "Monday"
case Tuesday:
return "Tuesday"
case Wednesday:
return "Wednesday"
case Thursday:
return "Thursday"
case Friday:
return "Friday"
case Saturday:
return "Saturday"
default:
return "Unknown"
}
}
func main() {
day := Sunday
fmt.Println(day) // Sunday (not 0)
fmt.Println(Wednesday) // Wednesday (not 3)
fmt.Println(Saturday) // Saturday (not 6)
// Out-of-range value
var invalid Weekday = 8
fmt.Println(invalid) // Unknown
}
Scoring Log¶
Record your performance honestly. Include the date and whether you used hints.
| Exercise | Date | Score | Used Hints? | Notes |
|---|---|---|---|---|
| Exercise 1 — Zero Value Survey | — | —/1 | — | — |
| Exercise 2 — Type Inference Inspector | — | —/1 | — | — |
| Exercise 3 — Days of the Week | — | —/1 | — | — |
| Exercise 4 — Temperature Converter | — | —/2 | — | — |
| Exercise 5 — Permission Flags | — | —/2 | — | — |
| Exercise 6 — Type Conversion Roundtrip | — | —/2 | — | — |
| Exercise 7 — Integer Overflow | — | —/3 | — | — |
| Exercise 8 — Swap Without Temp | — | —/3 | — | — |
| Exercise 9 — Custom Enum | — | —/5 | — | — |
| Total | —/20 |
Passing threshold: 13/20 (65%). Aim for 17/20 (85%) before taking the test.