Keentune
Go, oriented
12 chapters
·
about 18 min read
·
free
Go is defined more by what it refuses than by what it offers. No inheritance, no exceptions, no implicit conversions, no constructors, and for its first decade no generics. Each refusal replaces a choice with a convention, which is the actual product: unfamiliar Go reads like the Go you already know. So the fastest way in is not a syntax tour but a list of what was removed and what you use instead. Nearly everything that frustrates newcomers is one of these refusals meeting a habit from Java or Python — reaching for a try block, expecting a subclass, expecting a warning where Go gives a build failure.
Each chapter opens with the short version. Tap one to read the detail.
The compiler as a co-author
~2 min
An unused variable or import is a build error, not a warning. Visibility is decided entirely by whether a name starts with a capital letter. Both rules exist to remove a decision, and both catch newcomers who expect the compiler to be advisory.
Distinct types, explicit conversions, and the constants that soften them
~2 min
Every variable is usable the moment it is declared, because every type has a zero value. A defined type is genuinely new even when its underlying type is familiar, so mixing it with that underlying type needs an explicit conversion — and untyped constants are what stop that rule being unbearable.
One loop, a switch that stops, and a range that copies
~2 min
for is the only loop keyword, and switch breaks at the end of every case. The one to internalise is range: it hands you a copy of each element, so writing to the loop variable changes nothing.
Everything is copied, and a pointer is how you opt out
~2 min
Arguments are always copied, so a function cannot change what you passed unless you pass a pointer. Pointers have no arithmetic and cannot dangle: returning the address of a local is safe, because the compiler decides what lives on the heap.
Slices, maps and strings: what copying one actually copies
~2 min
Arrays and structs copy in full. Slices, maps and strings are descriptors, so copying one copies a header and both copies see the same data. That single distinction explains the aliasing surprises, the nil-map panic, and why a string's length is not its character count.
Composition instead of inheritance
~2 min
Embedding promotes an inner type's fields and methods, which looks like inheritance and is not: there is no subtype relationship, so an outer type is never usable where the inner one is required. Receivers decide mutation, and pointer receivers decide what satisfies an interface.
Interfaces are satisfied by accident, and generics are the other tool
~2 min
There is no implements clause: having the methods is the whole requirement, so interfaces belong to the code that consumes them. An interface value is a type-and-value pair, which produces the one trap everyone hits — an interface holding a nil pointer is not nil.
Errors are values, and defer is not a finally block
~2 min
There is no throw. An error is an ordinary return value checked immediately, and wrapping with %w is what keeps it inspectable. defer runs on every exit path including a panic — but its arguments are captured when the defer executes, not when it runs.
Goroutines are cheap; channels are what makes them safe
~2 min
go f() starts a concurrent call and gives you nothing back — no handle, no identifier, no way to stop it. A goroutine ends only by returning, so every one you start needs a reason it will finish. Channels both pass values and order the two sides.
Shared memory, and why "it worked" proves nothing
~2 min
A data race is two goroutines touching one location with at least one write and no ordering between them. It does not reliably misbehave — racy code often prints the right answer — so correctness comes from the tools, not from observation. context carries cancellation down a call tree.
The toolchain, and the test culture that comes with it
~2 min
Builds are reproducible without a lock file, because the resolver picks the highest version something actually *requires* rather than the newest published. Tests need no framework: a table of cases run through t.Run, compared with if got != want, is the whole convention.
Standard-library contracts worth knowing before you hit them
~2 min
A few library contracts are unusual enough to cause bugs that survive testing. The sharpest: Read may return bytes *and* io.EOF in the same call, so a loop that checks the error before using the count can silently drop the last chunk.
See the full Go curriculum
Written by Keentune. We are not affiliated with or endorsed by the organizations whose documentation informs this guide, and any linked sources belong to their respective owners.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC