Learn / Go Service Engineering
Go Language and Data Foundations
A consolidated foundations lesson preserving 4 focused PTesting lessons without duplicating an unrelated authored PTLearn topic.
Course: Go Service Engineering. Level: Intermediate. Topic: Reliable services.
Stage: basic - Foundation - Language and runtime foundations. Connect go language and data foundations to the professional workflow for Go Services.
Outcomes
- Describe the design goals that make Go useful for services and tools.
- Identify packages, imports, functions, and the main entry point.
- Run, format, and reason about a minimal Go program.
- Declare variables and constants idiomatically.
- Use if, switch, for, range, slices, maps, and structs.
- Explain when pointer fields and pointer parameters are useful.
- Write functions with clear inputs, outputs, and error returns.
- Attach methods to named types using value or pointer receivers.
- Use closures for local behavior without obscuring control flow.
- Explain slice length, capacity, and append behavior.
- Use maps for lookup tables and sets.
- Model data with structs, tags, and constructors when needed.
Concepts
- Orientation and Go's Design
- Go's bias toward boring clarity
- The go command
- Go Programming foundations
- Guided practice
- Syntax Basics
- Declarations and zero values
- Control flow is small but expressive
- Structs and pointers
- Functions and Methods
- Functions return values explicitly
- Methods are functions with receivers
- Data Structures
- Slices are descriptors over arrays
- Maps and sets
Concept flow
Show how language and runtime foundations moves from trigger to implementation outcome in Go Services.
- Language model
- Runtime behavior
- Engineering decision
- Verification evidence
Session flow
- Model Orientation and Go's Design (concept, 75 min) — Name the decisions behind Orientation and Go's Design before writing code.
- Describe the design goals that make Go useful for services and tools.
- Explain where Orientation and Go's Design belongs in ticket processing platform.
- Build the vertical slice (walkthrough, 135 min) — Implement the smallest useful slice in legacy/go-services/go-language-and-data-foundations.txt.
- Identify packages, imports, functions, and the main entry point.
- Connect Go's bias toward boring clarity to a working example.
- Verify and harden (exercise, 90 min) — Expected output: 0
- Run, format, and reason about a minimal Go program.
- Record one risk or follow-up before moving on.
- Why Go and Your First Program: Go's bias toward boring clarity (concept, 28 min) — Go was designed for teams building long-lived software. Its small syntax, fast compiler, standard formatter, explicit error handling, and built-in concurrency support reduce the number of style debates in a codebase. The language usually favors local readability over clever abstraction.
- Go source files are organized into packages.
- A command starts in package main with function main.
- The standard toolchain handles formatting, testing, documentation, and modules.
- The course examples are meant for local Go tooling. Guided exercises in this platform validate safe deterministic metadata or text answers only.
- Retained source example: hello.go
package main
import "fmt"
func main() {
fmt.Println("hello, go")
}
Expected output: hello, go
Every executable Go program has a main package and a main function.
- Why Go and Your First Program: The go command (walkthrough, 28 min) — The go command is the front door to the toolchain. You will use go run for small programs, go test for tests, gofmt for formatting, go mod for dependency metadata, and go doc or pkg.go.dev for API documentation. Professional Go projects lean on these defaults rather than custom scripts.
- Retained source example: Common local commands
go run .
gofmt -w .
go test ./...
go list ./...
These commands run the current command, format source, run all tests, and list packages.
- Why Go and Your First Program: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Start with the standard toolchain before adding custom build systems.
- Practice: Run gofmt on every source file.
- Practice: Keep first programs small enough that package, import, and function boundaries are obvious.
- Avoid: Treating Go as a class-based object-oriented language.
- Avoid: Skipping gofmt and creating unnecessary style variation.
- Avoid: Expecting guided exercises here to execute arbitrary submitted code.
- Why Go and Your First Program: references (review, 2 min) — Original references retained from the legacy library.
- The Go Programming Language: https://go.dev/
- Effective Go: https://go.dev/doc/effective_go
- Values, Control Flow, and Structs: Declarations and zero values (concept, 30 min) — Go variables always have a type and always have a value. If you do not provide an explicit initializer, the variable receives its zero value: 0 for numbers, false for bool, empty string for string, nil for slices, maps, pointers, channels, interfaces, and functions. This makes many types usable immediately when their zero value is meaningful.
- Retained source example: Declarations
var retries int
timeoutSeconds := 3
const serviceName = "orders"
fmt.Println(retries, timeoutSeconds, serviceName)
Expected output: 0 3 orders
Use := inside functions when the type is clear from the initializer.
- Values, Control Flow, and Structs: Control flow is small but expressive (walkthrough, 30 min) — Go has one loop keyword: for. It covers classic counted loops, while-style loops, infinite loops, and range iteration. switch statements do not fall through by default, which keeps branches explicit.
- Retained source example: Range and switch
scores := []int{9, 10, 7}
total := 0
for _, score := range scores {
total += score
}
switch avg := total / len(scores); {
case avg >= 9:
fmt.Println("excellent")
case avg >= 7:
fmt.Println("solid")
default:
fmt.Println("needs practice")
}
Expected output: solid
A switch can omit the switch expression and use boolean case conditions.
- Values, Control Flow, and Structs: Structs and pointers (walkthrough, 30 min) — A struct groups named fields. Use values when copying is cheap and identity does not matter. Use pointers when a function needs to mutate a value, avoid copying a larger value, or represent optional presence with nil. Do not use pointers everywhere by habit.
- Retained source example: Struct update
type User struct {
ID string
Email string
Admin bool
}
func Promote(u *User) {
u.Admin = true
}
Promote receives *User because it modifies the caller's User value.
- Values, Control Flow, and Structs: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Prefer short variable declarations inside functions when the type is obvious.
- Practice: Check the ok result for map lookups when missing keys are meaningful.
- Practice: Design structs so the zero value is useful when practical.
- Avoid: Taking pointers to everything without a mutation, identity, or performance reason.
- Avoid: Confusing nil slices with empty slices in API responses.
- Avoid: Ignoring the loop variable capture issue when starting goroutines from a range loop.
- Values, Control Flow, and Structs: references (review, 2 min) — Original references retained from the legacy library.
- Go Tour: Basics: https://go.dev/tour/basics/1
- Go Spec: Declarations and scope: https://go.dev/ref/spec#Declarations_and_scope
- Functions, Methods, and API Shape: Functions return values explicitly (concept, 38 min) — Go functions make inputs and outputs visible in the signature. Multiple return values are common, especially when the final return value is an error. Named result parameters exist but should be used sparingly because they can hide what a function returns.
- Retained source example: Function with validation
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("divide by zero")
}
return a / b, nil
}
The caller must inspect the error before trusting the quotient.
- Functions, Methods, and API Shape: Methods are functions with receivers (walkthrough, 38 min) — A method is a function attached to a named type. Use a value receiver when the method does not mutate the receiver and copying is acceptable. Use a pointer receiver when the method mutates state, avoids a large copy, or must satisfy an interface consistently with other pointer methods.
- Retained source example: Value and pointer receivers
type Counter struct {
n int
}
func (c Counter) Value() int {
return c.n
}
func (c *Counter) Inc() {
c.n++
}
Value reads can use a value receiver; mutation requires a pointer receiver.
- Functions, Methods, and API Shape: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Keep function signatures honest and small.
- Practice: Use pointer receivers consistently when any method on a type requires mutation.
- Practice: Return early on invalid inputs to reduce nesting.
- Avoid: Using named returns in long functions where they reduce clarity.
- Avoid: Mixing value and pointer receivers without understanding interface method sets.
- Avoid: Using closures to hide important control flow.
- Functions, Methods, and API Shape: references (review, 2 min) — Original references retained from the legacy library.
- Effective Go: Functions: https://go.dev/doc/effective_go#functions
- Go Spec: Method declarations: https://go.dev/ref/spec#Method_declarations
- Slices, Maps, and Struct Modeling: Slices are descriptors over arrays (concept, 40 min) — A slice is a small descriptor containing a pointer to an array, a length, and a capacity. append may reuse the existing backing array or allocate a new one. Because slices share backing arrays, be careful when retaining subslices of large buffers or modifying overlapping slices.
- Retained source example: Length and capacity
names := make([]string, 0, 3)
names = append(names, "Ada", "Ken")
fmt.Println(len(names), cap(names))
Expected output: 2 3
len is the number of elements; cap is available space before reallocation may be needed.
- Slices, Maps, and Struct Modeling: Maps and sets (walkthrough, 40 min) — Maps provide hash-based lookup. A common set representation is map[T]struct{} because the empty struct takes no storage for each value. Maps are not safe for concurrent writes without synchronization.
- Retained source example: Set with a map
seen := map[string]struct{}{}
seen["request-123"] = struct{}{}
if _, ok := seen["request-123"]; ok {
fmt.Println("duplicate")
}
Expected output: duplicate
map[string]struct{} is an idiomatic set for strings.
- Slices, Maps, and Struct Modeling: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Preallocate slices when you know an approximate size.
- Practice: Use comma-ok lookups when zero values are ambiguous.
- Practice: Use small structs to name domain concepts instead of passing loose maps through your code.
- Avoid: Forgetting append returns the updated slice.
- Avoid: Retaining a tiny subslice of a very large buffer.
- Avoid: Writing maps from multiple goroutines without synchronization.
- Slices, Maps, and Struct Modeling: references (review, 2 min) — Original references retained from the legacy library.
- Go Blog: Go Slices: https://go.dev/blog/slices-intro
- Go Blog: JSON and Go: https://go.dev/blog/json
Code example
go in legacy/go-services/go-language-and-data-foundations.txt.
package main
func main() {}
Walkthrough examples
- Go Language and Data Foundations in a ticket processing platform — A team is extending a small Go service plus worker and needs this lesson's pattern to be clear enough for review, testing, and future maintenance.
- File: legacy/go-services/go-language-and-data-foundations.txt
- File: tests/go-language-and-data-foundations.spec
- File: docs/go-services/go-language-and-data-foundations.md
- Start from the provided go snippet and make the intent visible in names and boundaries.
- Apply the checklist item "Explain the language or runtime rule in your own words" before adding extra behavior.
- Write down how the implementation changes when Go's bias toward boring clarity fails or becomes slow.
- hello.go — Every executable Go program has a main package and a main function.
- Retained source code:
package main
import "fmt"
func main() {
fmt.Println("hello, go")
}
- Expected output: hello, go
- Compare the example with the canonical PTLearn implementation.
- Common local commands — These commands run the current command, format source, run all tests, and list packages.
- Retained source code:
go run .
gofmt -w .
go test ./...
go list ./...
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Package and import shape — Grouped imports are common when a file uses multiple packages.
- Retained source code:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("built at", time.Now().UTC().Format(time.RFC3339))
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Declarations — Use := inside functions when the type is clear from the initializer.
- Retained source code:
var retries int
timeoutSeconds := 3
const serviceName = "orders"
fmt.Println(retries, timeoutSeconds, serviceName)
- Expected output: 0 3 orders
- Compare the example with the canonical PTLearn implementation.
- Range and switch — A switch can omit the switch expression and use boolean case conditions.
- Retained source code:
scores := []int{9, 10, 7}
total := 0
for _, score := range scores {
total += score
}
switch avg := total / len(scores); {
case avg >= 9:
fmt.Println("excellent")
case avg >= 7:
fmt.Println("solid")
default:
fmt.Println("needs practice")
}
- Expected output: solid
- Compare the example with the canonical PTLearn implementation.
- Struct update — Promote receives *User because it modifies the caller's User value.
- Retained source code:
type User struct {
ID string
Email string
Admin bool
}
func Promote(u *User) {
u.Admin = true
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Map lookup with ok — The comma-ok form distinguishes a missing key from a present zero value.
- Retained source code:
ports := map[string]int{"http": 80, "https": 443}
port, ok := ports["grpc"]
if !ok {
port = 50051
}
fmt.Println(port)
- Expected output: 50051
- Compare the example with the canonical PTLearn implementation.
- Function with validation — The caller must inspect the error before trusting the quotient.
- Retained source code:
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("divide by zero")
}
return a / b, nil
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Value and pointer receivers — Value reads can use a value receiver; mutation requires a pointer receiver.
- Retained source code:
type Counter struct {
n int
}
func (c Counter) Value() int {
return c.n
}
func (c *Counter) Inc() {
c.n++
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Closure for filtering — Higher-order functions are useful when the callback keeps behavior local and readable.
- Retained source code:
func Filter(nums []int, keep func(int) bool) []int {
out := make([]int, 0, len(nums))
for _, n := range nums {
if keep(n) {
out = append(out, n)
}
}
return out
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Length and capacity — len is the number of elements; cap is available space before reallocation may be needed.
- Retained source code:
names := make([]string, 0, 3)
names = append(names, "Ada", "Ken")
fmt.Println(len(names), cap(names))
- Expected output: 2 3
- Compare the example with the canonical PTLearn implementation.
- Set with a map — map[string]struct{} is an idiomatic set for strings.
- Retained source code:
seen := map[string]struct{}{}
seen["request-123"] = struct{}{}
if _, ok := seen["request-123"]; ok {
fmt.Println("duplicate")
}
- Expected output: duplicate
- Compare the example with the canonical PTLearn implementation.
- JSON-ready struct — Struct tags provide metadata consumed by packages such as encoding/json.
- Retained source code:
type CreateUserRequest struct {
Email string `json:"email"`
Name string `json:"name"`
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
Practice
- Identify the entry point: Which package and function form the entry point for an executable Go command?
- Hint: Look at the first line and function name in a tiny executable program.
- Reference solution: An executable command uses package main and starts in func main().
- Accepted answers: package main and func main | main package and main function | package main func main
- Name a zero value: What is the zero value of an int variable in Go?
- Expected output: 0
- Hint: Numeric types default to their additive identity.
- Reference solution: The zero value of int is 0.
- Accepted answers: 0 | zero
- Choose a receiver: A method increments a field on the receiver. Should it usually use a value receiver or pointer receiver?
- Hint: Mutation must affect the caller's value.
- Reference solution: Use a pointer receiver so the method can mutate the original value.
- Accepted answers: pointer receiver | pointer
- Slice descriptor fields: Name the two numeric properties commonly discussed for a slice descriptor.
- Hint: They are returned by built-in functions.
- Reference solution: The numeric properties are length and capacity, returned by len and cap.
- Accepted answers: length and capacity | len and cap | length capacity
Checklist
- Explain the language or runtime rule in your own words
- Run one focused example and record its output
- Name one failure mode or tradeoff
- Keep the verification evidence with the lesson
Quiz prompts
- A teammate wants to hide Orientation and Go's Design inside a convenient helper. What should you check first? — Place Orientation and Go's Design at the boundary that keeps ticket processing platform behavior explicit, testable, and reviewable.
- Which artifact best proves this Go Services lesson is ready for review? — Production-ready learning needs evidence: a test, trace, command, screenshot, or log that catches the risk again.
- What is the primary purpose of gofmt? — gofmt removes style debate by applying the standard Go formatting rules.
- Which package name is required for an executable command? — A command package must be named main and define func main().
- Which statement about switch in Go is true? — Go switch cases break automatically unless fallthrough is explicitly used.
- Why use the comma-ok form for map lookup? — A missing key returns the value type's zero value, so ok is needed when absence matters.
- What is the conventional final return value for a function that can fail? — Go convention places an error as the final return value.
- When is a pointer receiver usually appropriate? — Pointer receivers let the method modify the caller's value.
- What can append do when a slice has insufficient capacity? — append may allocate and return a slice pointing at a new backing array.
- Are ordinary Go maps safe for concurrent writes? — Concurrent map writes require synchronization such as a mutex or a different design.
Flashcards
- In Go Services, what should you remember about Orientation and Go's Design? Orientation and Go's Design matters here because it supports "Describe the design goals that make Go useful for services and tools.".
- In Go Services, what should you remember about Go's bias toward boring clarity? Go's bias toward boring clarity matters here because it supports "Identify packages, imports, functions, and the main entry point.".
- In Go Services, what should you remember about The go command? The go command matters here because it supports "Run, format, and reason about a minimal Go program.".
- In Go Services, what should you remember about Go Programming foundations? Go Programming foundations matters here because it supports "Declare variables and constants idiomatically.".
Labs
- Ship a go language and data foundations slice — Extend a small Go service plus worker with a small but reviewable feature that proves the lesson's architecture in code.
- Identify the entry point: Which package and function form the entry point for an executable Go command?
- Hint: Look at the first line and function name in a tiny executable program.
- Reference solution: An executable command uses package main and starts in func main().
- Accepted answers: package main and func main | main package and main function | package main func main
- Name a zero value: What is the zero value of an int variable in Go?
- Expected output: 0
- The implementation demonstrates Orientation and Go's Design without hidden global state.
- At least one test or verification step covers the main success path.
- The review notes explain the tradeoff behind Go's bias toward boring clarity.
Challenge
- Review-ready go language and data foundations (Core) — Turn the lesson work into a pull-request-sized change for ticket processing platform. Include the code, verification notes, one explicit tradeoff, and an updated concept diagram that names the riskiest handoff.
- All checklist items are either implemented or documented with a reason.
- The change can be understood from legacy/go-services/go-language-and-data-foundations.txt plus one short note.
- The concept diagram names ownership, failure handling, and verification points.
- A teammate could run the verification steps without asking for hidden context.
Canonical lesson URL