Basics & Packages
How a Go program is structured — packages and imports, variable and constant declarations, basic types, zero values, iota, identifier visibility, and the build tools.
9 questions
JuniorTheoryVery commonWhat are the zero values of Go's basic types?
What are the zero values of Go's basic types?
Every variable declared without an initializer gets its type's zero value, so Go has no uninitialised garbage. Numbers are 0, bool is false, and string is "" (empty, not nil). Pointers, slices, maps, channels, functions, and interfaces are nil. A struct's zero value has all fields zeroed, and an array's elements are each zeroed too.
Common mistakes
- ✗Thinking an uninitialised variable holds garbage — Go always zeroes it
- ✗Believing a zero-value
stringis nil rather than the empty string"" - ✗Forgetting that slices, maps, channels, and interfaces zero to
nil
Follow-up questions
- →What happens when you write to a nil map versus read from one?
- →Why can a nil slice still be appended to safely?
JuniorTheoryCommonWhat are Go's basic types, and what rules govern converting between them?
What are Go's basic types, and what rules govern converting between them?
Go's basic types are fixed-width integers (int, int8..int64, the uint family), floats (float32, float64), bool, and string, with byte an alias for uint8 and rune for int32. There is no implicit numeric conversion: you must convert explicitly, like float64(x), even between an int and an int64.
Common mistakes
- ✗Expecting an
intto widen into afloat64automatically without a cast - ✗Thinking
byteandruneare distinct types rather than aliases foruint8/int32 - ✗Assuming an
intand anint64are the same type and need no conversion
Follow-up questions
- →What can go wrong when you convert a
float64to anintin Go? - →How does the size of
intdepend on the target platform?
JuniorTheoryCommonWhat do go run, go build, and go mod each do in Go?
What do go run, go build, and go mod each do in Go?
go run compiles the program to a temporary location and runs it immediately, leaving no binary behind. go build compiles the package into a single self-contained executable on disk without running it. go mod manages the module and its dependencies, with subcommands like go mod init and go mod tidy.
Common mistakes
- ✗Thinking
go runleaves a binary on disk likego builddoes - ✗Believing a
go buildexecutable needs the Go runtime installed to run - ✗Confusing
go mod(dependency management) with a plain package downloader
Follow-up questions
- →What is the difference between
go mod initandgo mod tidy? - →How would you cross-compile a
go buildbinary for another OS or architecture?
JuniorTheoryCommonHow do var, const, and := differ for declaring values in Go?
How do var, const, and := differ for declaring values in Go?
var declares a variable with an explicit type or an inferred one from its initializer. const declares an immutable value fixed at compile time. := is short declaration with type inference, allowed only inside a function. At package scope you may use only var and const; := is a function-body form.
Common mistakes
- ✗Thinking
:=works at package scope — it is only valid inside a function - ✗Believing a
constcan be assigned a value computed at run time - ✗Forgetting that
varcan omit the type and infer it from the initializer
Follow-up questions
- →What is the difference between a typed and an untyped constant in Go?
- →When does
:=re-use an existing variable versus declare a new one?
JuniorTheoryCommonWhat is a Go package, and what do package main and import do?
What is a Go package, and what do package main and import do?
A package groups the .go files in one directory that are compiled together and share their identifiers. package main plus a func main() marks the executable entry point. import pulls in another package by its import path so you can call its exported names. An unused import is a compile error, not a warning.
Common mistakes
- ✗Thinking each file is its own package rather than the whole directory being one
- ✗Believing an unused import is only a warning — it fails the build
- ✗Forgetting that
package mainplusfunc main()is what makes a binary
Follow-up questions
- →How does an import path differ from the package name used to call it?
- →What does a blank import
import _ "pkg"do, and when is it used?
JuniorTheoryCommonHow does Go decide whether an identifier is exported from a package?
How does Go decide whether an identifier is exported from a package?
Visibility is decided purely by the first letter of the name. An identifier whose first letter is uppercase is exported and visible from other packages; a lowercase first letter keeps it package-private. The rule applies uniformly to functions, types, variables, constants, struct fields, and methods — there is no public/private keyword.
Common mistakes
- ✗Thinking Go has
public/privatekeywords instead of a capitalisation rule - ✗Believing struct fields are always visible regardless of their first letter
- ✗Assuming the rule applies only to functions, not to fields, methods, or consts
Follow-up questions
- →Can an exported struct have unexported fields, and what does that imply?
- →How do
internal/packages further restrict who may import a package?
JuniorCodeOccasionalWhat values do the constants get from iota in these two const blocks?
What values do the constants get from iota in these two const blocks?
A=0, B=1, D=3 — iota starts at 0 in each const block and increments by one per line, including the skipped _ line (which consumes value 2). In the second block KB = 1 << (10 * (iota + 1)) gives KB=1024 and MB=1048576, because iota is 0 then 1 on successive lines.
Common mistakes
- ✗Thinking a skipped
_line does not advanceiota— it still consumes a value - ✗Believing
iotais a global counter rather than resetting perconstblock - ✗Forgetting
iotais evaluated inside the full line expression, e.g.1 << (10*(iota+1))
Follow-up questions
- →How would you define
KB, MB, GBas power-of-two sizes using oneiotaexpression? - →What happens to
iotaif two constants share a single line viaA, B = iota, iota?
MiddleTheoryOccasionalHow do typed constants and iota build an enum in Go, and how do they differ from untyped constants?
How do typed constants and iota build an enum in Go, and how do they differ from untyped constants?
A const block with iota generates successive values per line. Typing the first const (type Weekday int; Sunday Weekday = iota) makes the whole column that typed enum. The gotcha: an untyped constant implicitly converts to whatever type the context needs, but a TYPED constant does not. So mixing a Weekday value with a plain int is a compile error — you need an explicit int(Sunday).
Common mistakes
- ✗Thinking a named enum type and a plain
intinterchange without an explicit conversion - ✗Forgetting
iotaresets perconstblock, not per line, and increments each line - ✗Confusing untyped-constant implicit conversion with typed-constant strictness
Follow-up questions
- →How do you give an enum readable names, e.g. via a
String()method? - →What makes an untyped constant's default type, and when does it apply?
MiddleTheoryRareWhy can := in an inner scope silently shadow an outer variable?
Why can := in an inner scope silently shadow an outer variable?
:= always declares NEW variables in the current block. If a name from an outer scope is on its left, an inner := makes a fresh inner variable that shadows — not reassigns — the outer one, so writes through it never reach the outer variable. The fresh shadow starts at its zero value: a classic err-handling bug. Use = to assign, or declare once and reuse.
Common mistakes
- ✗Assuming an inner
:=on an outer name reassigns it instead of declaring a shadow - ✗Using
:=inside anif/forblock and reading a stale outererrafterwards - ✗Believing the shadow inherits the outer value rather than starting at its zero value
Follow-up questions
- →Which Go tools or vet checks can flag an accidental shadow?
- →When does
:=legitimately reuse an outer name without shadowing it?