Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Variables and Types
Go is a statically typed language, meaning variable types are known at compile time. However, its syntax is designed to be concise, often inferring types for you. Think of Go’s type system like a strict but helpful librarian: it will not let you put a novel on the science shelf (type safety), but it also will not make you fill out a form every time you check out a book (type inference). You get safety without ceremony.Variable Declaration
There are three main ways to declare variables in Go.1. var keyword (Explicit Type)
Use this when you want to declare a variable without initializing it immediately, or when you want to be explicit about the type.
2. var keyword (Type Inferred)
If you provide an initial value, you can omit the type.
3. Short Declaration :=
Inside functions, you can use the := operator. This is the most common and idiomatic way to declare and initialize variables in Go.
:= is Preferred: It’s concise and lets the compiler infer types, making code cleaner while maintaining type safety.
Basic Types
Go has a rich set of built-in types.Integers
int,int8,int16,int32,int64uint,uint8,uint16,uint32,uint64,uintptr
int and uint are platform-dependent (32-bit on 32-bit systems, 64-bit on 64-bit systems). In 99% of cases, just use int. Only reach for specific-width types like int32 or int64 when you are dealing with serialization formats (protobuf, binary protocols), interacting with C code via cgo, or need to guarantee the bit width across platforms.
Floats
float32,float64
float64 is the default for floating-point numbers.
Booleans
bool(true or false)
Strings
string(immutable sequence of bytes, UTF-8 encoded)
Complex Numbers
complex64,complex128
Zero Values
Variables declared without an initial value are given their zero value. This is a key safety feature: Go does not have uninitialized variables. Every variable always has a well-defined value.| Type | Zero Value |
|---|---|
int | 0 |
float | 0.0 |
bool | false |
string | "" (empty string) |
| pointers | nil |
| slices | nil |
| maps | nil |
| channels | nil |
Constants
Constants are declared usingconst. They can be character, string, boolean, or numeric values.
:= syntax.
Iota
iota is a special identifier used to create enumerated constants. It resets to 0 whenever const appears and increments by 1 for each line.
Type Conversion
Go requires explicit type conversion. There is no implicit casting (e.g., you cannot assign anint to a float64 variable without casting). This is a deliberate design decision — implicit conversions are a notorious source of subtle bugs in C and JavaScript.
f = i, the compiler will throw an error.
Interview Deep-Dive
Go has zero values for all types. How does this design choice affect the way you write code compared to languages with null or undefined, and where can it bite you?
Go has zero values for all types. How does this design choice affect the way you write code compared to languages with null or undefined, and where can it bite you?
Strong Answer:
- Zero values mean every variable in Go is always initialized to a well-defined, usable value. An
intis0, astringis"", aboolisfalse, and astructhas all fields set to their respective zero values. This eliminates entire classes of “uninitialized variable” bugs that plague C and C++, and avoids the “billion dollar mistake” of null references in languages like Java. - Practically, this enables patterns like declaring a
bytes.Bufferor async.Mutexand using them immediately without any constructor call. The zero value IS the valid initial state. This is a deliberate design principle: “make the zero value useful.” - Where it bites you: a nil map reads fine (returns zero values) but panics on write. A nil slice is safe to append to but a nil pointer to a struct will panic when you access a field. And the most subtle gotcha: a nil pointer inside an interface is NOT a nil interface. If you return a
*MyError(nil)as anerrorinterface, the caller’serr != nilcheck returns true because the interface has a type even though its underlying value is nil. - In production, the zero-value principle means you need to think carefully about whether “zero” is a valid state for your domain. For example, a
UserIDof0might look valid to Go but represent “no user” in your business logic. Some teams use pointer fields or custom types to distinguish “not set” from “set to zero.”
float64(myInt)) because implicit numeric conversions are a notorious source of silent bugs. In C, assigning a float to an int silently truncates. In JavaScript, adding a number to a string silently concatenates. Go’s explicit conversions force you to acknowledge potential data loss at the call site. For example, converting an int64 to int32 can overflow silently, but at least the conversion is visible in the code and can be caught during review. The trade-off is verbosity: Go code has more explicit casts than Python or JavaScript. But in production systems handling financial data or sensor readings, silent numeric truncation can cause real damage — an explicit int32(bigValue) makes the risk visible.Explain how iota works in Go. Show me a non-trivial use case beyond simple sequential constants.
Explain how iota works in Go. Show me a non-trivial use case beyond simple sequential constants.
Strong Answer:
iotais a compile-time constant generator that resets to 0 at eachconstblock and increments by 1 for each constant specification (each line in the block). It is Go’s replacement for C-style enums.- The simple case is sequential enums:
Red = iotagives 0, 1, 2. But iota’s real power is that it can be used in expressions that are repeated implicitly. For example, you can define byte-size constants using1 << (10 * iota): skipping the zero value with_, KB becomes 1024, MB becomes 1048576, GB becomes 1073741824. - A practical production example is bitwise permission flags:
Read = 1 << iotagives you 1, 2, 4 which you can combine with OR operations:perms := Read | Write. This is exactly how Unix file permissions work, and it is how Go’s ownos.FileModeconstants are defined internally. - A more advanced pattern is using iota with a custom type and a
String()method to create self-documenting enums that print as names instead of numbers. You define atype Status int, assign values with iota, then implementfunc (s Status) String() stringusing a slice or map lookup. This makes logging and debugging much clearer.
Status parameter typed as int, what prevents callers from passing an invalid value like 999?Nothing at the type level — this is a real limitation. Since Status is just an alias for int, any int value can be assigned to it. The Go compiler will not reject Status(999). The defense is runtime validation: either check the value at API boundaries with a func (s Status) IsValid() bool method, or use the exhaustive switch pattern where your switch on the status has no default case and the exhaustive linter flags any missing cases. Some teams also define a _maxStatus sentinel as the last iota value and validate that s >= 0 && s < _maxStatus. This is a deliberate trade-off in Go’s design — simplicity over exhaustive compile-time checking. Languages with real sum types (Rust, Haskell) solve this at the type level, but Go chose not to add that complexity.A colleague writes `var x uint8 = 255; x++` and expects 256. What happens, why, and how would you defend against this in production code?
A colleague writes `var x uint8 = 255; x++` and expects 256. What happens, why, and how would you defend against this in production code?
Strong Answer:
- Go does not panic on integer overflow. The value wraps around silently according to modular arithmetic. So
uint8(255) + 1becomes0, not 256. For signed types,int8(127) + 1becomes-128. There is no runtime error, no warning, nothing. - This is inherited from C’s behavior and exists because overflow checking on every arithmetic operation would add significant runtime overhead. Go chose performance over safety here, unlike Rust which panics on overflow in debug mode and wraps in release mode.
- To defend against this in production: First, use
int(which is 64-bit on modern systems) unless you have a specific reason to use a smaller type. The range ofint64is large enough that overflow is practically impossible for most business logic. Second, when working with user input or wire protocols that use fixed-width integers, add explicit bounds checks before arithmetic:if x > math.MaxUint8 - increment { return ErrOverflow }. Third, for critical financial calculations, consider usingmath/bigfor arbitrary-precision arithmetic. Fourth, themathpackage provides constants likemath.MaxInt64,math.MaxUint32, etc., for guard checks. - The real-world scenario where this burns people: processing counters or metrics that monotonically increase. If you use a
uint32counter for HTTP requests and your service handles 100K requests per second, you overflow in about 12 hours.
string is a read-only slice of bytes, typically UTF-8 encoded but not guaranteed to be. len("hello") returns the byte count, not the character count. For ASCII this is the same, but for multi-byte UTF-8 characters like emoji or CJK text, len("...") gives a larger number than expected. Iterating with a byte index (s[i]) gives individual bytes, which can split a multi-byte character. The correct way to iterate over characters (runes) is for i, r := range s, which decodes UTF-8 on the fly. For character counting, use utf8.RuneCountInString(s). For string manipulation at the rune level, convert to []rune first, but be aware that this allocates a new slice. In production APIs that handle international text, always be explicit about whether your “length” means bytes or runes, and always validate that incoming strings are valid UTF-8 using utf8.Valid().