Illustrated technology atlas · Go (Golang) language fundamentals
Golang fundamentals · illustrated cheat sheets
General
A technically literate engineer’s fundamentals pack: 8 topics across language basics, applications, basic production cautions and evidence-led improvements. Original examples target Go 1.22+ and avoid newer APIs. Current official documents were opened on 07 Oct 2026; historical Go blog posts are used only for still-supported mechanisms, checked against current references. The supplied Deep Learning JSON and HTML define the notebook palette and layout philosophy, not Go facts. Code fragments have been reviewed against documentation, but were not compiled or executed because a Go toolchain is unavailable in this environment. Framework surveys, runtime internals and academic completeness are outside this fundamentals scope.
Research date: 2026-10-07
Conceptual overview inspired by your supplied notebook metadata. The slice windows redraw views into shared storage; they do not represent independent array copies. The numbered SVG sheets below give exact mechanisms and source-linked examples.
Part 1 · Fundamentals
Programs, types and values
Prerequisites: Start here / basic technical literacy
How a program is organized
Package and module boundaries are logical organization. The diagram is not a prescribed repository tree. [S01][S02][S22]
Go in one sentence verified
Go is a statically typed, compiled language with garbage collection and built-in support for concurrent programs.
Read a small program and locate its boundaries: a module records dependency requirements, packages organize code, and an executable uses package main with func main. Capitalized identifiers are exported.
var n int starts at 0; bool starts at false; string starts empty. := declares local variables with at least one new name in its scope. = assigns. for handles counted loops, conditions and range. Functions may return multiple values.
Arguments are passed by value, including pointers. A copied pointer can still mutate the object it points to. Copying a struct also copies any fields that refer to shared data; it is not a recursive clone.
Given a small service, start with explicit parameters and concrete values; introduce shared pointers only where identity or mutation matters. Watch for := shadowing an outer variable. Next, split one business calculation into its own package. This is a teaching recommendation, not a required directory convention.
Keep this: Go passes values. A copied value can still refer to shared data.
Check yourself: Does passing *Counter make Go pass-by-reference?
No. The pointer value is copied. Both pointer values can address the same Counter, which explains visible mutation.
Part 2 · Fundamentals
Collections and shared storage
Prerequisites: 01-programs-values
Two views, one backing array
The example uses a fixed array so the shown lengths, capacities and aliases are exact. The copy example uses int elements. [S05][S07][S06]
Objective and mental model verified
[N]T is a fixed-size array value; []T describes a segment of backing storage. len counts visible elements, and cap counts accessible elements from that segment start. append returns the resulting slice; it may reuse storage or allocate a new array.
arr := [4]int{10, 20, 30, 40}
a, b := arr[:2], arr[1:3]
b[0] = 99 // arr[1] and a[1] are both 99
own := make([]int, len(a))
copy(own, a) // own has independent integer elements
A map requires comparable keys. A missing key produces the value type’s zero value; v, ok := m[k] distinguishes missing from present-zero. Reading a nil map is allowed; assigning an entry panics. Iteration order is unspecified. Concurrent access involving a write needs synchronization.
Strings hold immutable bytes. len("Go✓") is 5 bytes; []rune("Go✓") has 3 code points. String range decodes runes and reports their byte offsets. A rune is not necessarily one user-perceived character; combining marks and emoji can span several code points.
When callers need independent integer elements, allocate and copy. For slices of pointers, copying elements still shares pointed-to objects. Next, predict len, cap and observable mutations before running a short aliasing experiment; preallocate only when a useful size estimate exists.
Keep this: A slice copy is a new view, not independent element storage.
Check yourself: What is the capacity of arr[1:3] for a four-element array?
3: accessible storage starts at array index 1 and extends through index 3. Its visible length is 2.
Part 3 · Fundamentals
Structs, methods, interfaces and generics
Prerequisites: 01-programs-values
Capabilities and method sets
The automatic address-taking call convenience does not change interface satisfaction. [S03][S08]
Why an interface can be non-nil
Think dynamic type plus dynamic value; do not infer the runtime’s physical storage representation. [S04]
Objective and mental model verified
Use structs for data and methods for behavior. An interface is satisfied by the required method set without an implements declaration. Composition connects capabilities without a class inheritance tree.
A pointer receiver can mutate the caller’s object. If Inc has receiver *Counter, only *Counter satisfies an interface requiring Inc. The convenient c.Inc() rewrite for an addressable Counter variable does not add Inc to the Counter value method set.
An interface can hold a dynamic type and a dynamic value. A nil *Counter stored in any keeps the dynamic type *Counter, so the interface is not nil. Successful error returns should return nil explicitly. The two-part diagram is a semantic mental model, not a memory-layout guarantee.
Given a caller that needs interchangeable implementations, name only its required methods. Generics fit an unchanged algorithm operating on several allowed types; interfaces fit interchangeable behavior. First build one concrete implementation, then add a fake through a small interface when testing needs it. This is a scope-based design recommendation.
The example uses os.ReadFile, while the defer panel teaches the lifetime rule for resources acquired explicitly. [S09][S10][S11]
Mental model verified
An ordinary failure travels as an error value, commonly beside a result. The caller checks it and chooses how to respond. A readable early return keeps the success path visible.
fmt.Errorf with %w preserves an inspectable cause. errors.Is tests matching errors through wrapping; errors.As locates an error assignable to a target type. Comparing error strings makes code depend on presentation. Decide deliberately which underlying causes belong in your API contract.
Deferred arguments are evaluated when defer executes. Deferred calls run in reverse registration order as the surrounding function exits. This makes cleanup adjacent to acquisition. Deferred calls inside a long-running loop wait for the function to exit, not each iteration.
For repeated resource work, put each iteration’s acquisition and cleanup in a small helper. Handle write/flush/close failures where they affect correctness. Reserve panic for exceptional conditions rather than expected input failures. Next, inject a missing file or invalid payload and inspect the returned context and cause.
Arrows show value flow. The results channel is closed by its coordinator after every sending worker has finished. [S12][S23]
Send and receive state table
Buffered open receives wait only when empty. Closed receives return ok=false only after the buffered values are drained. [S02][S12]
Mental model verified
go f() starts an independent goroutine and does not wait for its result. Concurrency describes independent progress; parallelism describes simultaneous execution. A concurrent design can run on one core.
Unbuffered communication needs a matching sender and receiver. A buffered channel decouples them until full or empty. Each value is received once, not broadcast to every worker. A sender-side owner closes only after all sends finish; a coordinator can wait for multiple senders before closing results.
out := make(chan int, 1)
go func() { out <- 3 * 7 }()
value := <-out // value is 21
// This exchange does not need close: no receiver ranges until closure.
select picks a ready communication case; when several are ready, it offers no source-order priority. A nil channel case cannot proceed. Receiving a closed channel drains buffered values before returning zero with ok=false. Sending to a closed channel panics. Never race channel closure against unfinished sends.
For independent I/O jobs, use a bounded worker count and an explicit shutdown path; more workers also cost memory and downstream capacity. If a consumer stops early, blocked upstream sends can leak goroutines. Next, add cancellation and a join, then test early exit instead of only the happy path.
Keep this: Coordinate independent work and define who sends, receives, closes and waits.
Check yourself: If two workers read one jobs channel, does each job reach both workers?
No. A given sent value is consumed by one receive operation. Broadcasting requires a separate design.
Part 6 · Best practices
Cancellation, joining and shared state
Prerequisites: 05-goroutines-channels
Cancellation is observed at a decision point
The diagram shows a cooperative cancellation point, not forced termination. The displayed sheet snippet is a one-shot sketch and handles a closed jobs channel. [S13][S14]
Mental model verified
A derived context carries a deadline and cancellation signal. Parent cancellation propagates to children. Call the returned cancel function to release associated resources. A cancellation signal does not force a goroutine to stop or wait for its completion; the work must cooperate.
ctx, cancel := context.WithTimeout(parent, time.Second)
defer cancel()
select {
case job, ok := <-jobs:
if !ok { return nil }
return process(ctx, job)
case <-ctx.Done():
return ctx.Err()
}
// process must itself observe ctx where it blocks.
A consistent mutex protects every conflicting access to an invariant. A WaitGroup coordinates completion, not access to a map or counter. Add before launching in the classic Add/Done pattern; wait for all work before releasing shared ownership. Do not copy mutexes or WaitGroups after first use.
type Counter struct { mu sync.Mutex; n int }
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}
// Reads of n must use the same synchronization discipline.
Given shared mutable counters, a mutex is often the clearest baseline; a channel fits ownership transfer and work coordination. Keep critical sections short and avoid waiting on external I/O while locked. A cancellation case has no priority if another select case is ready. Next, test cancellation while waiting for a job and while sending a result.
The sequence is an example workflow, not a dependency imposed by the tools. [S15][S16][S20][S21]
Test boundary behavior verified
Tests live in *_test.go and use functions such as TestTotal(*testing.T). Cover meaningful boundaries: nil/empty input, ordinary values and failures. Benchmarks investigate cost; fuzz tests explore generated inputs. Tests should assert observable behavior, not simply repeat implementation steps.
go fmt ./...
go mod tidy
go vet ./...
go test ./...
go test -race ./...
go build ./...
# Known-vulnerability check, after installation:
govulncheck ./...
A passing -race run detects no races on the paths and schedules it exercised; it cannot prove the absence of races. Race support depends on platform and its toolchain requirements. Keep dependency files in version control. Next, add cancellation tests and fuzz input parsing; assess reachable govulncheck findings in their actual context.
This is an example design and diagnostic mapping. No performance numbers or improvement claims are implied. [S18][S19]
Reference application — reasoned design synthesis
Build one small quote endpoint: an HTTP handler parses and validates, a service applies the pricing rule, and an adapter talks to storage or an upstream service. Pass request context across operations. This separation is a suggested testable baseline, not mandatory ceremony for a tiny health handler.
net/http supplies handlers and request contexts. Use explicit limits and timeout settings suited to the endpoint. ReadHeaderTimeout bounds header reading; it does not bound all handler execution. Add request-body size limits and cancellation-aware downstream operations where applicable.
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
// Method-qualified patterns require Go 1.22+.
// Test through mux with httptest; no framework is required.
CPU profiles show active CPU cost. Heap profiles inspect sampled allocations and memory. Goroutine stacks, blocking profiles and execution traces help investigate waiting. A slow network operation may be nearly invisible in a CPU profile. Match the tool to the suspected mechanism.
Record a representative baseline, identify a bottleneck, change one thing, then rerun behavior and performance checks. Preallocating a known-size result slice may reduce allocations; a CPU-heavy loop may need a different algorithm. Neither is a verified improvement until measured. Report workload, toolchain and trade-offs.