1. What will be the output of this code snippet?
m := map[string]int{"a": 1}
delete(m, "b")
fmt.Println(len(m))
Output: 1.
delete(m, key) is safe even when the key doesn’t exist. Deleting a missing key is a no-op — it does not panic, does not error, and does not change the map.
The map still holds its single entry "a": 1, so len(m) is 1.
The contrast: delete on a missing key is safe, but writing to a nil map panics (a different question). Deletion — like reading — is designed to be forgiving. The interview answer: 1 — deleting a missing key is a harmless no-op.
2. How can you enable the built-in Go data race detector during tests or builds?
Answer: Pass the -race flag: go test -race, go run -race, or go build -race.
The race detector instruments your program at compile time and, at runtime, monitors all memory accesses for conflicting unsynchronized reads/writes across goroutines. When it detects a race, it reports the exact conflicting accesses with stack traces.
Usage patterns:
go test -race ./...— the most common: run the test suite under race detection.go run -race/go build -race— instrument a program directly.
The detector is built into the toolchain (no import needed) and adds modest runtime overhead, so it’s standard practice to run tests with -race in CI. The interview answer: the -race flag on go test, go run, or go build.
3. What will println(1 << 3) evaluate to?
Output: 8.
The left-shift operator << moves the bits of a value left by the given number of positions, filling the vacated bits with zeros. Each left shift by one position multiplies by 2.
1 << 3 shifts the binary 1 left by three places: 1 → 10 (2) → 100 (4) → 1000 (8). Mathematically that’s 1 × 2^3 = 8.
The output is 8. The interview point: x << n equals x × 2^n, and 1 << n is the classic way to write 2^n as a constant.
4. What is the outcome of passing a struct to a function by value vs by pointer?
Answer: By value copies the entire struct contents onto the stack; by pointer passes just the address (8 bytes on 64-bit systems).
When you pass a struct by value, Go copies every field into the new parameter — for a large struct that’s a lot of copying and stack usage. The function works on its own copy; mutations don’t reach the caller.
When you pass a pointer (*Struct), the function receives the address — a single word (8 bytes on 64-bit platforms). No field copying. The function reads and mutates the original struct through the pointer.
The practical trade-off:
- Small structs — by value is fine and cache-friendly.
- Large structs — by pointer avoids the copy cost and allows mutation.
- Semantics — by value gives you an immutable-by-default view (the caller’s copy is untouched unless fields are mutable inside); by pointer is how you mutate the caller’s instance.
The interview answer: value copies the whole struct to the stack; pointer passes an 8-byte address, enabling mutation and avoiding the copy.
5. What happens if you run select {} (an empty select statement) in a standalone Go program with no other active goroutines?
Answer: It blocks the current goroutine forever, and the runtime raises a fatal deadlock panic.
select with no cases has nothing to do and no default to escape to. It waits — indefinitely. With no other goroutines running (and even with some, if they also never make the empty select proceed), the program is stuck with all goroutines blocked.
The runtime detects this global standstill and panics: fatal error: all goroutines are asleep - deadlock!. Like all deadlock panics, it’s non-recoverable.
select {} is the canonical way to write “block forever” — used deliberately in some daemon-style main functions. If nothing else keeps the process alive, it’s the deadlock. The interview answer: blocks forever → fatal deadlock panic.
6. What is the standard convention for error handling return values in Go functions?
Answer: Functions return (result, error) as multi-value returns, and callers check the error against nil. Go has no exceptions or try/catch.
Error handling in Go is explicit and value-based. The idiom:
val, err := doSomething()
if err != nil {
// handle
}
The conventions:
- The
erroris the last return value. nilmeans success; a non-nil error means failure.- Every caller checks the error explicitly — no hidden propagation.
This is a deliberate design contrast with exception-based languages: errors are ordinary values you can store, wrap, compare, and return, and you can’t accidentally ignore them (the _ discard is explicit). The Go 1.13 errors.Is/errors.As additions make wrapping and inspecting error chains first-class. The interview answer: multi-value returns with error last, checked against nil — no exceptions.
7. What does errors.Is(err, targetErr) do in modern Go error handling?
Answer: It walks the error chain — following Unwrap() — and returns true if any error in the chain matches targetErr.
Go 1.13 added error wrapping: fmt.Errorf("...: %w", err) creates a chain of errors. errors.Is makes checking that chain easy:
if errors.Is(err, os.ErrNotExist) {
// err, or anything it wraps, is a "not exist" error
}
It’s the replacement for err == targetErr in the presence of wrapping. Instead of comparing just the top error (which would fail once the error is wrapped), Is recursively unwraps and tests each layer — and it works with custom error types that implement an Is(target) bool method for fine-grained matching.
The interview answer: errors.Is recursively unwraps the error chain and reports whether targetErr appears anywhere in it.
8. What does errors.As(err, &targetStruct) accomplish?
Answer: It searches the error chain for an error matching the type of targetStruct and, on success, assigns that error to the target.
errors.As is the type-based complement of errors.Is (which is value-based). You pass a pointer to a variable of the type you’re looking for:
var notFound *NotFoundError
if errors.As(err, ¬Found) {
// notFound is the matched error, typed as *NotFoundError
}
As unwraps the chain and looks for the first error whose type is assignable to the target. When found, it sets the target to that error. This is how you recover a specific error type out of a wrapped chain — the Go analog of “catch this exception type.”
The interview answer: errors.As walks the chain, finds the first error of the target type, and assigns it to the target variable.
9. What is the function of the iota identifier in constant declarations?
Answer: iota is a constant generator — it starts at 0 at the beginning of a const block and increments by 1 for each successive line.
const (
A = iota // 0
B // 1
C // 2
)
Key properties:
- It resets to
0at the start of eachconstblock. - It increments per const spec line (blank lines and comments don’t increment it).
- It’s a compile-time construct — used in expressions that get evaluated when constants are formed.
- You can skip values with
_(blank identifier).
The canonical uses are enumerated values and bit flags:
const (
Read = 1 << iota // 1
Write // 2
Exec // 4
)
The interview answer: iota is a per-block, zero-based constant counter that increments each line.
10. What is the value of KB in this iota bitwise calculation?
const (
_ = 1 << (10 * iota)
KB
MB
)
Output: 1024.
iota increments per line starting at 0. The block reads:
- Line 0 (
_):iota = 0, so1 << (10 * 0)=1 << 0=1. Discarded via_. - Line 1 (
KB):iota = 1, so1 << (10 * 1)=1 << 10=1024. (Each constant spec on its own line implicitly repeats the previous expression.) - Line 2 (
MB):iota = 2, so1 << 20= 1048576.
So KB is 1024 — the classic idiom for building byte-size constants (KB = 2^10). The interview answer: 1024.
11. What does runtime.Gosched() do when called inside a goroutine?
Answer: It yields the processor — the current goroutine voluntarily gives up the CPU and is placed back in the runnable queue, letting other goroutines run.
runtime.Gosched() is a cooperative yield: “I don’t need to run right now; let someone else go.” The calling goroutine is moved to the back of the runnable queue, and the scheduler picks another goroutine. When its turn comes, it resumes.
Two clarifications:
- It does not pause or sleep — the goroutine stays runnable, just queued.
- It does not terminate the goroutine or trigger GC.
Its uses are niche — mostly in tight CPU loops or hand-rolled cooperative scheduling where you want to give other goroutines a chance without blocking. In practice, idiomatic Go rarely needs it, since channel/mutex operations already release the processor. The interview answer: Gosched voluntarily yields the CPU, letting other goroutines run, with the caller queued to resume later.
12. What is the zero value of a function variable in Go?
Answer: nil.
Function variables — like slices, maps, channels, pointers, and interfaces — zero-value to nil. A declared-but-unassigned function variable holds nil, meaning “no function.”
var f func(int) // f == nil
f(1) // panic: nil function
Calling a nil function variable panics at runtime. The safe pattern is to check before calling: if f != nil { f(x) }. This is how Go supports optional callbacks — a nil function field signals “no handler registered.”
The interview answer: nil is the zero value of a function variable; calling it panics.
13. What will fmt.Println(m == nil) output after running this code?
var m map[string]int
fmt.Println(m == nil)
Output: true.
A var m map[string]int declaration without initialization creates a nil map — its zero value. Unlike slices (which can’t be compared except to nil), maps can be compared to nil, and here the answer is true.
What you can do safely with a nil map:
len(m)→0.- Reading a key
m["k"]→ the zero value (withok == false).
What panics: writing to a nil map — m["k"] = 1 → panic: assignment to entry in nil map (the next question).
The interview answer: true — an uninitialized map is nil.
14. What happens when you attempt to write a key to a nil map in Go?
Answer: A runtime panic: panic: assignment to entry in nil map.
A nil map has no internal storage to hold an entry. Writing to it can’t succeed, and Go panics rather than guessing. This is unrecoverable unless caught — and the standard guidance is to never write to a nil map.
The rule to remember, stated as a pair:
- Reading from a nil map is safe (returns zero values).
- Writing to a nil map panics.
The fix is initialization before first write: m = make(map[string]int) or m := map[string]int{}. This is the single most common map bug in Go. The interview answer: writing to a nil map panics with assignment to entry in nil map.
15. What is the result of applying append() to a slice without reassigning the return value (append(s, 1))?
Answer: A compile error — Go requires the return value of append to be used.
append may reallocate: when the slice is full, it returns a header pointing at a brand-new, larger backing array. The original s header still points at the old array, which may or may not have received the element. To make the result usable, you must capture it: s = append(s, 1).
Go enforces this discipline at compile time. If you write append(s, 1) as a bare statement and ignore the result, the compiler rejects it with something like append result not used. (In Go 1.22+, the diagnostic mentions the assignment explicitly.)
This is deliberate: an ignored append is almost certainly a bug, because the caller has no reliable way to know whether the element actually landed. The interview answer: a compile error — append’s result must be captured with s = append(s, x).
Premium Content
Unlock Top 50 - Part 2 and all premium lessons with a subscription.
From ₹199.99/year — See plans