1. What is the main purpose of the go vet tool in the Go toolchain?
Answer: go vet is a static analysis tool that inspects Go source and reports suspicious constructs — unreachable code, mismatched arguments, printf format errors, and similar issues.
go vet doesn’t run your program; it examines the source statically, looking for code that compiles but is likely wrong. Its diagnostics catch a family of classic mistakes:
fmt.Printfcalls whose format verbs don’t match the argument types.- Unreachable code (statements after
return). - Suspicious
append/copymisuse, unused struct fields in composite literals, and so on. - Unsafe pointer arithmetic that could misbehave.
It’s part of the standard toolchain — go test automatically runs a subset of vet’s checks, and most CI setups run go vet as a first-line correctness gate.
The interview answer: go vet is the static analysis tool for suspicious, wrong-but-compiling code — distinct from gofmt (formatting) and go test (running tests).
2. What is the result of applying the cap() function to a channel?
Answer: cap(ch) returns the channel’s buffer capacity — 0 for an unbuffered channel, and the declared buffer size for a buffered one.
cap works on slices, arrays, pointers to arrays, and channels. For a channel, it reports the capacity declared at creation:
make(chan int)— cap 0 (unbuffered).make(chan int, 5)— cap 5.
The complement is len(ch), which returns the number of elements currently queued in the buffer — how many sends are waiting for receives. So cap is the fixed size of the buffer; len is how full it is right now.
The interview answer: cap(ch) gives the channel’s buffer capacity; len(ch) gives the current number of buffered, unread elements.
3. What is the correct syntax for an inline Type Switch in Go?
Answer:
switch v := i.(type) {
case int:
// ...
}
A type switch inspects an interface value’s dynamic type. The syntax is distinctive: the type assertion’s special form i.(type) — note type is a literal keyword here, not a variable name — and v is bound to the value typed as the matched concrete type.
func describe(i any) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("int %d", v) // v is int
case string:
return fmt.Sprintf("string %q", v) // v is string
default:
return "unknown"
}
}
Within each case, the bound variable v has the corresponding concrete type, so you can use it without casting. If you only care about the type, you can omit the binding: switch i.(type).
The interview answer: switch v := i.(type) { case T: ... } — with (type) as the literal type-assertion form.
4. What will happen if you attempt to close a nil channel?
Answer: A runtime panic: panic: close of nil channel.
close requires a real, initialized channel. A nil channel (the zero value, or an uninitialized declared channel) has no runtime object to close, so the runtime panics immediately.
This is one of the three classic channel misuse panics, alongside sending on a closed channel and closing an already-closed channel. The rules to remember:
close(nil)→ panic.close(ch)twice → panic on the second close.- send on a closed channel → panic.
The safe pattern is to structure code so exactly one goroutine (the sender) owns closing, once, when sending is done. The interview answer: close on a nil channel panics.
5. What is the default visibility of variables, functions, or struct fields named with an initial lowercase letter?
Answer: Unexported (package-private) — visible only within the package where they’re declared.
Go has no public/private keywords. Visibility is encoded in the identifier’s first letter:
- Uppercase first letter (
Person,NewServer,ErrNotFound) — exported: accessible from other packages. - Lowercase first letter (
person,newServer,errNotFound) — unexported: visible only inside the declaring package.
This applies uniformly to variables, functions, methods, struct fields, and type names. It’s the language’s single, consistent access-control mechanism, and it’s checked at compile time — importing a package and referencing a lowercase name is a compile error.
The interview answer: lowercase names are package-private; uppercase names are exported. The first letter is the visibility declaration.
6. Which package provides atomic primitives for lock-free concurrency operations?
Answer: sync/atomic.
The standard library package is sync/atomic. It provides lock-free primitives that map to hardware atomic instructions:
atomic.AddInt64(&x, 1)— atomic increment.atomic.LoadInt64/atomic.StoreInt64— atomic read/write.atomic.CompareAndSwapInt64(&x, old, new)— CAS.atomic.Value— atomic storage of any type.
These are the building blocks for lock-free algorithms and for safely sharing counters/flags across goroutines without a mutex. They trade the simplicity of a lock for finer-grained, non-blocking operations.
The interview answer: sync/atomic — low-level lock-free atomic memory operations.
7. What is the output of the following array initialization?
arr := [...]int{1, 2, 3, 4}
fmt.Println(reflect.TypeOf(arr).Kind())
Output: Array.
The [...] ellipsis tells the compiler to infer the array length from the number of elements in the literal. arr is a fixed-size array of type [4]int — a value type with 4 elements.
The distinction matters:
[4]int{...}— array, fixed length, known at compile time.[]int{...}— slice, a dynamic view over an underlying array.[...]int{...}— array with compiler-inferred length.
So reflect.TypeOf(arr).Kind() reports array. The output is Array. The interview point: [...] produces an array, not a slice — length inferred from the literal.
8. How do you implement method overriding in Go structs?
Answer: Go has no class inheritance, so there’s no override keyword. The idiomatic mechanism is struct embedding (composition): embed a struct, and the outer struct promotes the inner methods; declare a method with the same name to shadow it.
type Base struct{}
func (Base) Speak() string { return "base" }
type Child struct {
Base // embedding — promotion
}
func (Child) Speak() string { return "child" } // shadows Base.Speak
Child gets Base.Speak for free via promotion, but defines its own Speak, which wins when called on a Child. This gives a similar outcome to method overriding, but the model is composition, not inheritance:
- Promoted methods become part of the outer type’s method set.
- An outer method with the same name shadows the inner one (outer wins).
- There’s no virtual dispatch or
super; you reach the embedded method explicitly asc.Base.Speak().
The interview answer: Go uses struct embedding for composition, with same-named methods on the outer struct shadowing the embedded ones.
9. What happens when calling append() on a nil slice?
Answer: It works fine — append allocates a new underlying array and returns a valid, initialized slice.
nil is a legitimate slice value (len 0, cap 0, no backing array), and append is designed to accept it. Since there’s no capacity, the first append allocates a fresh backing array, copies nothing (there’s nothing to copy), writes the new element, and returns a slice header pointing at the new storage.
var s []int // nil
s = append(s, 1) // s is now [1], backed by a real array
No panic, no nil-pointer dereference. This is why the “append to a nil slice” pattern is idiomatic for building a slice incrementally.
The interview answer: append on a nil slice allocates the backing array and returns a valid slice.
10. How does Go scheduler allocate OS threads to goroutines?
Answer: M:N scheduling — M goroutines are multiplexed onto N OS threads.
Go’s runtime scheduler is built on the M:P:G model:
- G — a goroutine (a lightweight stack + state).
- M — an OS thread (the actual execution unit the kernel knows).
- P — a logical processor (a context that holds a runnable goroutine queue; its count defaults to the number of logical CPUs, controlled by
GOMAXPROCS).
A goroutine doesn’t map 1:1 to a thread. The scheduler multiplexes many goroutines onto a small number of threads — an M picks up a G from a P’s queue, runs it until it blocks or yields, then picks up another G. When a goroutine blocks on I/O, the M parks and another M starts, so the CPU stays busy.
This M:N design is what makes goroutines cheap: you can spawn thousands or millions, and the runtime efficiently shares a handful of threads among them.
The interview answer: M:N scheduling — goroutines (G) multiplexed onto OS threads (M) via logical processors (P).
11. What is the role of GOMAXPROCS environment variable?
Answer: It controls the number of OS threads that can execute user-level Go code simultaneously — effectively the number of logical CPUs (Ps) available to the scheduler.
GOMAXPROCS sets how many parallel execution contexts the Go scheduler runs. By default it matches the machine’s logical CPU count, which is almost always right. You can read/change it at runtime via runtime.GOMAXPROCS(n).
What it does not control: per-goroutine stack size, GC heap thresholds, or network limits. It’s purely about CPU parallelism for user code. Raising it above the core count rarely helps and can hurt (more thread-switching); lowering it below the core count deliberately limits parallelism.
The interview answer: GOMAXPROCS sets the number of OS threads running user Go code concurrently (defaults to logical CPUs).
12. What will fmt.Printf(“%T”, x) display for variable x := ‘A’?
Output: int32.
A single-quoted literal like 'A' is a rune — a Unicode code point. In Go, rune is an alias for int32, so 'A' has type int32 (with value 65).
The distinction:
'A'— rune literal, typeint32. Go treats characters as numbers."A"— string literal, typestring(a byte sequence).
%T prints the concrete type, so the output is int32. Not uint8 (that’s byte — what you’d get from "A"[0]), not string, and there is no char type in Go. The interview answer: int32, because single quotes denote a rune.
13. What is the result of using copy(dst, src) when dst is an empty slice ([]int{})?
Answer: 0 elements are copied — nothing happens.
copy(dst, src) copies at most min(len(dst), len(src)) elements. The destination’s length is the hard limit; copy never grows dst.
With dst := []int{} (length 0), the minimum is 0 regardless of src’s length, so zero elements are copied. dst stays empty.
This is a classic trap for people who expect copy to behave like append and resize the destination. It doesn’t — you must allocate dst with sufficient length first:
dst := make([]int, len(src)) // correct
copy(dst, src)
The interview answer: 0 elements are copied, because copy is bounded by the destination’s length.
14. Which statement regarding init() functions in Go is correct?
Answer: init() functions run automatically when a package is initialized — before main() — and can appear multiple times per file and per package.
init() has a very specific shape and schedule:
- It takes no arguments and returns nothing.
- It runs automatically — you never call it explicitly.
- A package’s
inits run when the package is loaded, after its imported dependencies’inits, and before any of its exported functions are used ormain()starts. - You can declare multiple
init()functions across the files of a package (or multiple in one file); they run in file/declaration order within a package.
init is the place for package-level setup — registering things, initializing globals that need computation — though overuse is a code smell because it makes initialization implicit.
The interview answer: init() runs automatically at package initialization, before main, takes no args, returns nothing, and may appear multiple times.
15. What is the output of fmt.Println(string(65))?
Output: A.
Converting an integer to string in Go treats the integer as a Unicode code point and produces the single-character string for it.
string(65) converts the code point 65 (ASCII ‘A’) into the string "A". fmt.Println prints A.
The pitfall: this is not a decimal formatting operation. If you wanted the text "65", you’d use strconv.Itoa(65) or fmt.Sprintf("%d", 65). string(n) is purely the rune-to-string conversion.
A caveat that extends the point: string(65) is fine, but string(0x1F600) produces the emoji string; and string(10) produces a string containing a newline character — each integer maps to the character with that code point.
The interview answer: A — string(65) converts the integer as a Unicode code point.
Premium Content
Unlock Top 50 - Part 1 and all premium lessons with a subscription.
From ₹199.99/year — See plans