Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Types & Tooling
GO

Types & Tooling

Practice questions covering Go types, modules, go mod, go vet, the compiler, and essential Go development tooling.

1. What is the key structural difference between make() and new() in Go?

Answer: make() creates and initializes the built-in reference types (slices, maps, channels) and returns the initialized type itself. new() allocates zeroed storage for any type and returns a pointer to it.

These two built-ins solve different problems and are often confused.

new(T):

  • Allocates zeroed memory for type T.
  • Returns a pointer *T to that zeroed value.
  • new(int) gives you *int pointing to 0. It works for any type, but for slices/maps/channels it’s almost useless: new([]int) is a *[]int pointing to a nil slice.

make(T, ...):

  • Only works for slices, maps, and channels.
  • Initializes the internal data structure — a slice gets its backing array, a map its buckets, a channel its buffer.
  • Returns the initialized value (a []int, a map[K]V, a chan T), not a pointer.

The mental model: new gives you zeroed memory; make gives you a usable reference type. You’d write make([]int, 5) for a slice and new(int) for a pointer to an int — and you’d essentially never write new([]int).

Answer:

make() creates and initializes the built-in reference types (slices, maps, channels) and returns the initialized type itself. new() allocates zeroed storage for any type and returns a pointer to it.

These two built-ins solve different problems and are often confused.

new(T):

  • Allocates zeroed memory for type T.
  • Returns a pointer *T to that zeroed value.
  • new(int) gives you *int pointing to 0. It works for any type, but for slices/maps/channels it’s almost useless: new([]int) is a *[]int pointing to a nil slice.

make(T, ...):

  • Only works for slices, maps, and channels.
  • Initializes the internal data structure — a slice gets its backing array, a map its buckets, a channel its buffer.
  • Returns the initialized value (a []int, a map[K]V, a chan T), not a pointer.

The mental model: new gives you zeroed memory; make gives you a usable reference type. You’d write make([]int, 5) for a slice and new(int) for a pointer to an int — and you’d essentially never write new([]int).

2. What will the following loop print?

for i := 0; i < 3; i++ {
    defer func() {
        fmt.Print(i, " ")
    }()
}

Output: 3 3 3

The deferred functions are closures — they capture the variable i, not the value of i at the time the defer executed. All three closures reference the same loop variable.

The defers run after the loop finishes, in LIFO order. By then, the loop has run i through 0, 1, 2 and the loop condition (i < 3) has pushed it to 3. Every closure reads the shared variable’s current value — 3. Each prints 3 .

This was such a common bug that Go 1.22 changed loop variable semantics (each iteration now gets its own variable). Before 1.22, the output here was 3 3 3. To get 2 1 0 under old semantics you’d capture the value explicitly:

defer func(v int) { fmt.Print(v, " ") }(i)

The interview point: closures capture variables, not values — combined with LIFO defer, a loop of deferred closures sees the final loop value.

Answer:

3 3 3

The deferred functions are closures — they capture the variable i, not the value of i at the time the defer executed. All three closures reference the same loop variable.

The defers run after the loop finishes, in LIFO order. By then, the loop has run i through 0, 1, 2 and the loop condition (i < 3) has pushed it to 3. Every closure reads the shared variable’s current value — 3. Each prints 3 .

This was such a common bug that Go 1.22 changed loop variable semantics (each iteration now gets its own variable). Before 1.22, the output here was 3 3 3. To get 2 1 0 under old semantics you’d capture the value explicitly:

defer func(v int) { fmt.Print(v, " ") }(i)

The interview point: closures capture variables, not values — combined with LIFO defer, a loop of deferred closures sees the final loop value.

3. What is the scope of a variable declared using := inside an if initialization statement?

Answer: It’s scoped to the if statement and all its accompanying else if / else branches.

Go allows an initialization statement in an if:

if err := doWork(); err != nil {
    // err in scope here
} else {
    // and here
}
// err NOT in scope here

The variable declared by := exists from the initialization through the end of the entire if-chain — the body, the else if clauses, and the else block. After the closing brace, it’s gone.

This is the idiomatic Go error-handling pattern: check an error, use it in the body, and don’t pollute the surrounding function scope. It compiles to clean, readable error handling.

The interview answer: the initialized variable lives only within the if (and its else/else if) block.

Answer:

It’s scoped to the if statement and all its accompanying else if / else branches.

Go allows an initialization statement in an if:

if err := doWork(); err != nil {
    // err in scope here
} else {
    // and here
}
// err NOT in scope here

The variable declared by := exists from the initialization through the end of the entire if-chain — the body, the else if clauses, and the else block. After the closing brace, it’s gone.

This is the idiomatic Go error-handling pattern: check an error, use it in the body, and don’t pollute the surrounding function scope. It compiles to clean, readable error handling.

The interview answer: the initialized variable lives only within the if (and its else/else if) block.

4. What is the size of struct{} (an empty struct) in Go?

Answer: 0 bytes.

struct{} is the empty struct — a type with no fields. An instance of it occupies zero bytes of memory. Go explicitly defines this: all empty structs share the same zero-sized representation, often a single shared address.

This makes it a perfect set-as-value signal. Two famous uses:

set := make(map[string]struct{})   // a set — keys matter, values cost nothing
ch := make(chan struct{})           // a "signal" channel — value carries no data

For the map, you only care about key presence; struct{} values waste nothing. For the channel, you’re using it purely for synchronization — close(ch) as a broadcast to all receivers, or ch <- struct{}{} as a “done” ping.

The interview answer: struct{} is exactly 0 bytes, and it’s used to signal presence without storage.

Answer:

0 bytes.

struct{} is the empty struct — a type with no fields. An instance of it occupies zero bytes of memory. Go explicitly defines this: all empty structs share the same zero-sized representation, often a single shared address.

This makes it a perfect set-as-value signal. Two famous uses:

set := make(map[string]struct{})   // a set — keys matter, values cost nothing
ch := make(chan struct{})           // a "signal" channel — value carries no data

For the map, you only care about key presence; struct{} values waste nothing. For the channel, you’re using it purely for synchronization — close(ch) as a broadcast to all receivers, or ch <- struct{}{} as a “done” ping.

The interview answer: struct{} is exactly 0 bytes, and it’s used to signal presence without storage.

5. What is the main utility of context.Context in Go service applications?

Answer: Propagating cancellation signals, deadlines, and request-scoped values across API boundaries and goroutines.

context.Context is the standard way to carry the lifecycle of a request through the call chain. Its three core capabilities:

  • Cancellationcontext.WithCancel creates a cancellable context; when the cancellation function is called (or the parent is cancelled), every goroutine holding the derived context learns about it via ctx.Done(). This is how an HTTP handler’s cancellation unwinds through downstream calls.
  • Deadlinescontext.WithTimeout / context.WithDeadline attach a time bound; operations should check the deadline and abort when it passes. This prevents runaway requests.
  • Valuesctx.Value(key) carries request-scoped data (request ID, auth principal) down the call stack, avoiding global state.

The idiomatic rules: context should be the first parameter of functions that do I/O or spawn work, and it’s used to coordinate cancellation rather than for passing arbitrary configuration. Libraries like the HTTP client and database/sql all honor ctx for cancellation and timeouts.

The interview answer: context propagates cancellation, deadlines, and scoped values across goroutines and API boundaries.

Answer:

Propagating cancellation signals, deadlines, and request-scoped values across API boundaries and goroutines.

context.Context is the standard way to carry the lifecycle of a request through the call chain. Its three core capabilities:

  • Cancellationcontext.WithCancel creates a cancellable context; when the cancellation function is called (or the parent is cancelled), every goroutine holding the derived context learns about it via ctx.Done(). This is how an HTTP handler’s cancellation unwinds through downstream calls.
  • Deadlinescontext.WithTimeout / context.WithDeadline attach a time bound; operations should check the deadline and abort when it passes. This prevents runaway requests.
  • Valuesctx.Value(key) carries request-scoped data (request ID, auth principal) down the call stack, avoiding global state.

The idiomatic rules: context should be the first parameter of functions that do I/O or spawn work, and it’s used to coordinate cancellation rather than for passing arbitrary configuration. Libraries like the HTTP client and database/sql all honor ctx for cancellation and timeouts.

The interview answer: context propagates cancellation, deadlines, and scoped values across goroutines and API boundaries.

6. What is the output of the following string indexing expression?

s := "hello"
fmt.Println(reflect.TypeOf(s[0]))

Answer: uint8 (a byte).

Indexing a Go string returns the raw byte at that position, typed as uint8 — the byte alias. It does not return a rune or a character.

This is a consequence of Go strings being sequences of bytes, not characters. s[0] gives the first byte of the UTF-8 encoding of "hello" — for ASCII, that byte happens to be the same numeric value as the character ('h' = 104).

The rune distinction matters for non-ASCII text: indexing a string containing "é" (two bytes in UTF-8) at position 0 gives a byte, not the full character. To iterate actual characters, use range:

for _, r := range s { ... }   // r is a rune (int32)

The interview answer: s[0] is a uint8/byte — raw bytes, not characters.

Answer:

uint8 (a byte).

Indexing a Go string returns the raw byte at that position, typed as uint8 — the byte alias. It does not return a rune or a character.

This is a consequence of Go strings being sequences of bytes, not characters. s[0] gives the first byte of the UTF-8 encoding of "hello" — for ASCII, that byte happens to be the same numeric value as the character ('h' = 104).

The rune distinction matters for non-ASCII text: indexing a string containing "é" (two bytes in UTF-8) at position 0 gives a byte, not the full character. To iterate actual characters, use range:

for _, r := range s { ... }   // r is a rune (int32)

The interview answer: s[0] is a uint8/byte — raw bytes, not characters.

7. Which garbage collection algorithm does the Go runtime use?

Answer: A concurrent tri-color mark-and-sweep collector — non-generational, designed for low pause times.

Go’s GC is built for the runtime’s concurrency: it runs concurrently with the program (application goroutines keep executing while the GC works), keeping pause times very short.

The tri-color scheme is the algorithm’s core: objects are colored white (unmarked), grey (marked but children not yet processed), or black (marked and processed). The collector repeatedly moves grey objects to black while marking their children, and at the end any remaining white objects are unreachable and swept away.

Two deliberate choices stand out:

  • Non-generational — no young/old object split (unlike Java’s generational collectors). This simplifies the runtime at the cost of some throughput.
  • Concurrent — marking and sweeping happen alongside program execution, with only tiny stop-the-world phases, which is why Go can hold pauses to sub-millisecond levels at scale.

The interview answer: concurrent tri-color mark-and-sweep — non-generational, low-latency by design.

Answer:

A concurrent tri-color mark-and-sweep collector — non-generational, designed for low pause times.

Go’s GC is built for the runtime’s concurrency: it runs concurrently with the program (application goroutines keep executing while the GC works), keeping pause times very short.

The tri-color scheme is the algorithm’s core: objects are colored white (unmarked), grey (marked but children not yet processed), or black (marked and processed). The collector repeatedly moves grey objects to black while marking their children, and at the end any remaining white objects are unreachable and swept away.

Two deliberate choices stand out:

  • Non-generational — no young/old object split (unlike Java’s generational collectors). This simplifies the runtime at the cost of some throughput.
  • Concurrent — marking and sweeping happen alongside program execution, with only tiny stop-the-world phases, which is why Go can hold pauses to sub-millisecond levels at scale.

The interview answer: concurrent tri-color mark-and-sweep — non-generational, low-latency by design.

8. What is the output of len(“Go-语言”) in Go?

Output: 9.

len() on a string counts bytes, not characters. The string is UTF-8 encoded, and the byte count is the sum of the bytes in each piece:

  • "Go-" — 3 ASCII characters, 1 byte each → 3 bytes.
  • "语" (yǔ, “language”) — 3 bytes in UTF-8.
  • "言" (yán, “speech”) — 3 bytes in UTF-8.

Total: 3 + 3 + 3 = 9.

The trap: the string has 5 visible characters, so an intuition from “length of text” would guess 5. But Go defines string length as raw byte count. This is why len on a string containing non-ASCII text gives a number larger than the character count.

The interview answer: 9 — the byte length of the UTF-8 string.

Answer:

9.

len() on a string counts bytes, not characters. The string is UTF-8 encoded, and the byte count is the sum of the bytes in each piece:

  • "Go-" — 3 ASCII characters, 1 byte each → 3 bytes.
  • "语" (yǔ, “language”) — 3 bytes in UTF-8.
  • "言" (yán, “speech”) — 3 bytes in UTF-8.

Total: 3 + 3 + 3 = 9.

The trap: the string has 5 visible characters, so an intuition from “length of text” would guess 5. But Go defines string length as raw byte count. This is why len on a string containing non-ASCII text gives a number larger than the character count.

The interview answer: 9 — the byte length of the UTF-8 string.

9. How do you find the character/rune count of a UTF-8 string in Go?

Answer: utf8.RuneCountInString(str).

Since len(str) counts bytes, counting actual characters requires decoding the UTF-8. The unicode/utf8 package provides exactly that: utf8.RuneCountInString(s) counts the Unicode code points (runes) in the string.

utf8.RuneCountInString("Go-语言")   // 5
len("Go-语言")                      // 9

Rune counting is also what happens implicitly when you range over a string — each iteration decodes one rune. The interview answer: utf8.RuneCountInString counts runes; len only counts bytes.

Answer:

utf8.RuneCountInString(str).

Since len(str) counts bytes, counting actual characters requires decoding the UTF-8. The unicode/utf8 package provides exactly that: utf8.RuneCountInString(s) counts the Unicode code points (runes) in the string.

utf8.RuneCountInString("Go-语言")   // 5
len("Go-语言")                      // 9

Rune counting is also what happens implicitly when you range over a string — each iteration decodes one rune. The interview answer: utf8.RuneCountInString counts runes; len only counts bytes.

10. What is the output of the following comparison?

var i interface{} = (*int)(nil)
fmt.Println(i == nil)

Output: false.

An interface value is nil only when both halves are nil: its dynamic type and its dynamic value. This is the classic typed-nil trap.

The assignment var i interface{} = (*int)(nil) wraps a nil pointer of type *int into the interface. Now the interface holds:

  • Dynamic type: *int — not nil.
  • Dynamic value: nil (the pointer is nil).

Because the type half is non-nil, the interface as a whole is not nil. So i == nil is false.

This is why “check if err is nil” has a famous gotcha: if a function returns a nil pointer typed as a concrete type inside an interface, the interface is non-nil even though the underlying pointer is nil. The safe habit is to return bare nil for error/interface values rather than typed nil pointers.

The interview answer: false — an interface is nil only if both its type and value are nil.

Answer:

false.

An interface value is nil only when both halves are nil: its dynamic type and its dynamic value. This is the classic typed-nil trap.

The assignment var i interface{} = (*int)(nil) wraps a nil pointer of type *int into the interface. Now the interface holds:

  • Dynamic type: *int — not nil.
  • Dynamic value: nil (the pointer is nil).

Because the type half is non-nil, the interface as a whole is not nil. So i == nil is false.

This is why “check if err is nil” has a famous gotcha: if a function returns a nil pointer typed as a concrete type inside an interface, the interface is non-nil even though the underlying pointer is nil. The safe habit is to return bare nil for error/interface values rather than typed nil pointers.

The interview answer: false — an interface is nil only if both its type and value are nil.

11. How does Go handle unused declared local variables inside function bodies?

Answer: It’s a compilation errordeclared and not used.

Go enforces cleanliness at compile time. Declaring a local variable and never using it stops the build with an error. The same rule applies to unused imports — you can’t import "fmt" and never use fmt.

This is stricter than most languages (which warn) and is a deliberate language design decision: unused variables and imports are almost always mistakes or dead code, so Go refuses to compile them, keeping codebases tidy and forcing you to confront the clutter.

The practical workarounds when you genuinely need to ignore a value: use the blank identifier — _ = x, or assign to _:

value, _ := m["key"]   // discard the ok flag

The interview answer: an unused local variable is a compile-time error (declared and not used); assign to _ to discard intentionally.

Answer:

It’s a compilation errordeclared and not used.

Go enforces cleanliness at compile time. Declaring a local variable and never using it stops the build with an error. The same rule applies to unused imports — you can’t import "fmt" and never use fmt.

This is stricter than most languages (which warn) and is a deliberate language design decision: unused variables and imports are almost always mistakes or dead code, so Go refuses to compile them, keeping codebases tidy and forcing you to confront the clutter.

The practical workarounds when you genuinely need to ignore a value: use the blank identifier — _ = x, or assign to _:

value, _ := m["key"]   // discard the ok flag

The interview answer: an unused local variable is a compile-time error (declared and not used); assign to _ to discard intentionally.

12. 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.Printf calls whose format verbs don’t match the argument types.
  • Unreachable code (statements after return).
  • Suspicious append/copy misuse, 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).

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.Printf calls whose format verbs don’t match the argument types.
  • Unreachable code (statements after return).
  • Suspicious append/copy misuse, 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).

13. 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.

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.

14. 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.

Answer:

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.

15. 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, type int32. Go treats characters as numbers.
  • "A" — string literal, type string (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.

Answer:

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, type int32. Go treats characters as numbers.
  • "A" — string literal, type string (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.

16. 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 or main() 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.

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 or main() 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.

17. 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: Astring(65) converts the integer as a Unicode code point.

Answer:

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: Astring(65) converts the integer as a Unicode code point.

18. 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.

Answer:

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.

19. 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: 110 (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.

Answer:

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: 110 (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.

20. 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 error is the last return value.
  • nil means 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.

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 error is the last return value.
  • nil means 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.

21. 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.

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.

22. 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, &notFound) {
    // 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.

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, &notFound) {
    // 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.

23. 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 0 at the start of each const block.
  • 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.

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 0 at the start of each const block.
  • 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.

24. 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, so 1 << (10 * 0) = 1 << 0 = 1. Discarded via _.
  • Line 1 (KB): iota = 1, so 1 << (10 * 1) = 1 << 10 = 1024. (Each constant spec on its own line implicitly repeats the previous expression.)
  • Line 2 (MB): iota = 2, so 1 << 20 = 1048576.

So KB is 1024 — the classic idiom for building byte-size constants (KB = 2^10). The interview answer: 1024.

Answer:

1024.

iota increments per line starting at 0. The block reads:

  • Line 0 (_): iota = 0, so 1 << (10 * 0) = 1 << 0 = 1. Discarded via _.
  • Line 1 (KB): iota = 1, so 1 << (10 * 1) = 1 << 10 = 1024. (Each constant spec on its own line implicitly repeats the previous expression.)
  • Line 2 (MB): iota = 2, so 1 << 20 = 1048576.

So KB is 1024 — the classic idiom for building byte-size constants (KB = 2^10). The interview answer: 1024.

25. 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.

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.

26. What is string immutability in Go?

Answer: The underlying byte contents of a string cannot be modified in place. s[0] = 'x' is invalid; string data is read-only.

A Go string is an immutable byte sequence. Once created, the bytes behind it can’t change — the language enforces this by making individual element assignment a compile error.

Consequences and workarounds:

  • s[i] is a read-only byte access. You cannot write through it.
  • To modify “a string,” you must convert to a mutable representation, change it, and convert back: b := []byte(s), mutate b, then s = string(b). The conversion copies the bytes (preserving immutability of the original).
  • Strings share storage freely and are safe to use across goroutines precisely because they can’t be mutated.

Note the immutability is about the contents, not the variable: s itself can be reassigned to a new string anytime. The interview answer: string bytes are immutable — reassignable variables, but no in-place content mutation.

Answer:

The underlying byte contents of a string cannot be modified in place. s[0] = 'x' is invalid; string data is read-only.

A Go string is an immutable byte sequence. Once created, the bytes behind it can’t change — the language enforces this by making individual element assignment a compile error.

Consequences and workarounds:

  • s[i] is a read-only byte access. You cannot write through it.
  • To modify “a string,” you must convert to a mutable representation, change it, and convert back: b := []byte(s), mutate b, then s = string(b). The conversion copies the bytes (preserving immutability of the original).
  • Strings share storage freely and are safe to use across goroutines precisely because they can’t be mutated.

Note the immutability is about the contents, not the variable: s itself can be reassigned to a new string anytime. The interview answer: string bytes are immutable — reassignable variables, but no in-place content mutation.

27. What will string([]rune{0x65e5, 0x672c}) produce?

Answer: The UTF-8 string "日本".

Converting a []rune to a string encodes each rune (Unicode code point) into its UTF-8 byte representation and concatenates them.

The runes here:

  • 0x65e5 is the Unicode code point for 日 (the Japanese/Chinese character “sun”).
  • 0x672c is 本 (the character for “book/origin”).

Together they form 日本 (“Japan”). The conversion produces the valid UTF-8 string containing those two characters.

The reverse direction works too — []rune("日本") gives back the code point slice. The interview answer: a UTF-8 string containing 日本.

Answer:

The UTF-8 string "日本".

Converting a []rune to a string encodes each rune (Unicode code point) into its UTF-8 byte representation and concatenates them.

The runes here:

  • 0x65e5 is the Unicode code point for 日 (the Japanese/Chinese character “sun”).
  • 0x672c is 本 (the character for “book/origin”).

Together they form 日本 (“Japan”). The conversion produces the valid UTF-8 string containing those two characters.

The reverse direction works too — []rune("日本") gives back the code point slice. The interview answer: a UTF-8 string containing 日本.

28. Which type of allocation determines whether a variable stays on the stack or moves to the heap?

Answer: The compiler’s escape analysis.

During compilation, Go’s escape analysis tracks each variable’s lifetime. If a variable’s address escapes the function frame — returned, stored in a global or heap-allocated object, captured by a closure that outlives the call — the variable must be heap-allocated. If it can’t escape, it stays on the stack.

The practical effects:

  • No escape → stack allocation. Cheap (just stack pointer adjustment), zero GC pressure.
  • Escape → heap allocation. Managed by the garbage collector.

The classic example: returning &localVar forces the variable to escape to the heap, because the caller will use it after the function returns. Go sometimes surprises developers with allocations in code that looks stack-safe — go build -gcflags=-m prints the analysis results and shows exactly what escapes.

The interview answer: compiler escape analysis decides stack vs. heap based on whether the variable’s lifetime exceeds its frame.

Answer:

The compiler’s escape analysis.

During compilation, Go’s escape analysis tracks each variable’s lifetime. If a variable’s address escapes the function frame — returned, stored in a global or heap-allocated object, captured by a closure that outlives the call — the variable must be heap-allocated. If it can’t escape, it stays on the stack.

The practical effects:

  • No escape → stack allocation. Cheap (just stack pointer adjustment), zero GC pressure.
  • Escape → heap allocation. Managed by the garbage collector.

The classic example: returning &localVar forces the variable to escape to the heap, because the caller will use it after the function returns. Go sometimes surprises developers with allocations in code that looks stack-safe — go build -gcflags=-m prints the analysis results and shows exactly what escapes.

The interview answer: compiler escape analysis decides stack vs. heap based on whether the variable’s lifetime exceeds its frame.

29. What does reflect.ValueOf(x).Kind() return?

Answer: The underlying primitive kind of the value — reflect.Struct, reflect.Slice, reflect.Int, and so on.

Go’s reflect package describes types on two levels:

  • Type — the full named type. reflect.TypeOf returns the custom type name (type User struct"User").
  • Kind — the underlying primitive kind the type is built on. reflect.ValueOf(u).Kind() for a User struct returns reflect.Struct.

So Kind answers “what fundamental category is this?” — Int, Float64, String, Slice, Map, Struct, Func, Chan, and the rest. Different named types (e.g., type Score int and type Count int) have different types but the same kind (Int).

This distinction is essential for generic-style reflection code that must behave based on the underlying structure. The interview answer: Kind() returns the primitive category (Struct, Slice, Int, …), independent of the custom type name.

Answer:

The underlying primitive kind of the value — reflect.Struct, reflect.Slice, reflect.Int, and so on.

Go’s reflect package describes types on two levels:

  • Type — the full named type. reflect.TypeOf returns the custom type name (type User struct"User").
  • Kind — the underlying primitive kind the type is built on. reflect.ValueOf(u).Kind() for a User struct returns reflect.Struct.

So Kind answers “what fundamental category is this?” — Int, Float64, String, Slice, Map, Struct, Func, Chan, and the rest. Different named types (e.g., type Score int and type Count int) have different types but the same kind (Int).

This distinction is essential for generic-style reflection code that must behave based on the underlying structure. The interview answer: Kind() returns the primitive category (Struct, Slice, Int, …), independent of the custom type name.

30. How does Go handle structure alignment and field order in memory?

Answer: Fields are aligned based on their type sizes, so field ordering affects total struct size via padding bytes.

Go aligns each field in memory to its natural alignment: an int64 on 8-byte boundaries, an int32 on 4, a byte on 1. The compiler inserts padding between fields to satisfy these alignments, and the struct’s total size is a multiple of its largest field’s alignment.

Consider:

struct { a byte; b int64; c byte }   // a at 0, pad 7, b at 8, c at 16, pad 7 → 24 bytes
struct { a byte; c byte; b int64 }    // a at 0, c at 1, pad 6, b at 8 → 16 bytes

Same three fields, different order — 24 vs 16 bytes. Ordering fields from largest to smallest minimizes padding.

Note: Go does not reorder fields for you; memory layout follows source order. This matters for performance-sensitive large structs and for serialization layouts. The interview answer: alignment-based padding means field order affects struct size; largest-first ordering reduces waste.

Answer:

Fields are aligned based on their type sizes, so field ordering affects total struct size via padding bytes.

Go aligns each field in memory to its natural alignment: an int64 on 8-byte boundaries, an int32 on 4, a byte on 1. The compiler inserts padding between fields to satisfy these alignments, and the struct’s total size is a multiple of its largest field’s alignment.

Consider:

struct { a byte; b int64; c byte }   // a at 0, pad 7, b at 8, c at 16, pad 7 → 24 bytes
struct { a byte; c byte; b int64 }    // a at 0, c at 1, pad 6, b at 8 → 16 bytes

Same three fields, different order — 24 vs 16 bytes. Ordering fields from largest to smallest minimizes padding.

Note: Go does not reorder fields for you; memory layout follows source order. This matters for performance-sensitive large structs and for serialization layouts. The interview answer: alignment-based padding means field order affects struct size; largest-first ordering reduces waste.

31. What is the output of the following boolean evaluation?

fmt.Println(true || false && false)

Output: true.

Precedence: && binds tighter than ||. So the expression groups as true || (false && false), not (true || false) && false.

Evaluate the && first: false && false is false. Then true || false is true.

The output is true. The short-circuit also means the right side never even runs its computation here — true || anything is immediately true. The interview point: && before ||, same rule as most languages.

Answer:

true.

Precedence: && binds tighter than ||. So the expression groups as true || (false && false), not (true || false) && false.

Evaluate the && first: false && false is false. Then true || false is true.

The output is true. The short-circuit also means the right side never even runs its computation here — true || anything is immediately true. The interview point: && before ||, same rule as most languages.

32. What is the purpose of testing.B in Go unit tests?

Answer: It’s the benchmark harness — testing.B controls benchmark loop iterations and measures execution time and memory allocations.

Benchmark functions have the signature func BenchmarkName(b *testing.B). The framework sets b.N — the number of iterations to run — adjusting it until the benchmark runs long enough for reliable timing. Your code runs the operation under test inside a loop over b.N:

func BenchmarkConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // operation being measured
    }
}

testing.B also exposes controls: b.ResetTimer() to exclude setup, b.N for the iteration count, b.ReportAllocs() to report memory allocations, and b.SetBytes for throughput metrics.

Run benchmarks with go test -bench=.. The interview answer: testing.B is the benchmarking type that controls iterations (b.N) and measures performance/allocation metrics.

Answer:

It’s the benchmark harness — testing.B controls benchmark loop iterations and measures execution time and memory allocations.

Benchmark functions have the signature func BenchmarkName(b *testing.B). The framework sets b.N — the number of iterations to run — adjusting it until the benchmark runs long enough for reliable timing. Your code runs the operation under test inside a loop over b.N:

func BenchmarkConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // operation being measured
    }
}

testing.B also exposes controls: b.ResetTimer() to exclude setup, b.N for the iteration count, b.ReportAllocs() to report memory allocations, and b.SetBytes for throughput metrics.

Run benchmarks with go test -bench=.. The interview answer: testing.B is the benchmarking type that controls iterations (b.N) and measures performance/allocation metrics.

33. What command measures code line coverage during test runs in Go?

Answer: go test -cover.

The -cover flag makes go test instrument the package, run the tests, and report what percentage of code statements were executed.

Usage: go test -cover prints a coverage percentage per package; go test -coverprofile=coverage.out writes detailed coverage data, which go tool cover -html=coverage.out renders as an annotated HTML report.

The interview answer: go test -cover computes and displays statement coverage for the tested packages.

Answer:

go test -cover.

The -cover flag makes go test instrument the package, run the tests, and report what percentage of code statements were executed.

Usage: go test -cover prints a coverage percentage per package; go test -coverprofile=coverage.out writes detailed coverage data, which go tool cover -html=coverage.out renders as an annotated HTML report.

The interview answer: go test -cover computes and displays statement coverage for the tested packages.

34. What is the main purpose of building applications with CGO_ENABLED=0?

Answer: It disables C interop, producing statically linked, zero-dependency pure Go binaries — ideal for minimal containers.

Cgo lets Go code call C libraries, which introduces a dependency on the C toolchain and dynamic libraries (libc). With CGO_ENABLED=0:

  • All C bindings are compiled out.
  • The result is a static binary with no external runtime dependencies — it runs in an empty container (scratch/distroless), alpine, or anywhere with a matching architecture.
  • Builds are more reproducible and the binary is self-contained.

Trade-offs: you lose the ability to use cgo-dependent packages (like os/user with cgo, some database drivers, net with cgo DNS) unless they have pure-Go fallbacks. The interview answer: CGO_ENABLED=0 produces static, dependency-free pure Go binaries for lightweight containerized deployments.

Answer:

It disables C interop, producing statically linked, zero-dependency pure Go binaries — ideal for minimal containers.

Cgo lets Go code call C libraries, which introduces a dependency on the C toolchain and dynamic libraries (libc). With CGO_ENABLED=0:

  • All C bindings are compiled out.
  • The result is a static binary with no external runtime dependencies — it runs in an empty container (scratch/distroless), alpine, or anywhere with a matching architecture.
  • Builds are more reproducible and the binary is self-contained.

Trade-offs: you lose the ability to use cgo-dependent packages (like os/user with cgo, some database drivers, net with cgo DNS) unless they have pure-Go fallbacks. The interview answer: CGO_ENABLED=0 produces static, dependency-free pure Go binaries for lightweight containerized deployments.

35. What is the output of fmt.Println(10 % -3) in Go?

Output: 1.

In Go, the sign of the remainder from % follows the sign of the dividend (the first operand).

10 % -3: the quotient truncates toward zero — 10 / -3 = -3.33... truncated to -3. Then 10 - (-3 * -3) = 10 - 9 = 1. The result is 1, positive because the dividend 10 is positive.

This contrasts with languages where the remainder takes the divisor’s sign. Go’s rule is simple to state: a % b has the sign of a. So -10 % 3 would be -1. The interview answer: 1 — remainder takes the dividend’s sign in Go.

Answer:

1.

In Go, the sign of the remainder from % follows the sign of the dividend (the first operand).

10 % -3: the quotient truncates toward zero — 10 / -3 = -3.33... truncated to -3. Then 10 - (-3 * -3) = 10 - 9 = 1. The result is 1, positive because the dividend 10 is positive.

This contrasts with languages where the remainder takes the divisor’s sign. Go’s rule is simple to state: a % b has the sign of a. So -10 % 3 would be -1. The interview answer: 1 — remainder takes the dividend’s sign in Go.

36. How do you convert a string variable s into a byte slice []byte without allocations using standard safe Go?

Answer: You don’t — the standard safe conversion []byte(s) allocates (it copies). There is no safe, allocation-free standard conversion.

The allocation exists to preserve string immutability: if []byte(s) aliased the string’s memory, writing to the byte slice would mutate the string behind its back. So the conversion copies.

b := []byte(s)   // safe, idiomatic — but allocates a copy

The unsafe alternative — unsafe.StringData / unsafe.Slice tricks — avoids the copy but creates an aliased, mutable view of immutable memory: modifying it is undefined behavior and violates Go’s memory safety. So the correct, safe, standard answer is []byte(s), accepting the allocation.

The interview answer: []byte(s). It allocates (a copy) — which is the correct price for safety; allocation-free conversion requires unsafe code and is not recommended.

Answer:

You don’t — the standard safe conversion []byte(s) allocates (it copies). There is no safe, allocation-free standard conversion.

The allocation exists to preserve string immutability: if []byte(s) aliased the string’s memory, writing to the byte slice would mutate the string behind its back. So the conversion copies.

b := []byte(s)   // safe, idiomatic — but allocates a copy

The unsafe alternative — unsafe.StringData / unsafe.Slice tricks — avoids the copy but creates an aliased, mutable view of immutable memory: modifying it is undefined behavior and violates Go’s memory safety. So the correct, safe, standard answer is []byte(s), accepting the allocation.

The interview answer: []byte(s). It allocates (a copy) — which is the correct price for safety; allocation-free conversion requires unsafe code and is not recommended.

37. What does the go.mod file specify in a Go project?

Answer: The module’s import path, the Go language version, and the module’s dependency requirements (direct and indirect).

go.mod is the module definition, the modern replacement for GOPATH. A typical file:

module example.com/proj

go 1.22

require (
    github.com/foo/bar v1.2.3
    golang.org/x/exp v0.0.0-...
)

It records:

  • Module path — the root import path for the module’s packages.
  • Go version — the language version the code targets (sets language feature gates).
  • Require block — every third-party dependency and its exact version.

The file is maintained by the Go toolchain (go get, go mod tidy). It works together with go.sum for reproducible builds. The interview answer: go.mod defines the module path, Go version, and dependency requirements.

Answer:

The module’s import path, the Go language version, and the module’s dependency requirements (direct and indirect).

go.mod is the module definition, the modern replacement for GOPATH. A typical file:

module example.com/proj

go 1.22

require (
    github.com/foo/bar v1.2.3
    golang.org/x/exp v0.0.0-...
)

It records:

  • Module path — the root import path for the module’s packages.
  • Go version — the language version the code targets (sets language feature gates).
  • Require block — every third-party dependency and its exact version.

The file is maintained by the Go toolchain (go get, go mod tidy). It works together with go.sum for reproducible builds. The interview answer: go.mod defines the module path, Go version, and dependency requirements.

38. What is the purpose of the go.sum file?

Answer: It stores cryptographic checksums of dependency module versions, ensuring builds are reproducible and tamper-proof.

go.sum contains a hash (SHA-256 based) for every module version the project depends on — both direct and transitive. When go downloads a module, it verifies the content against the recorded hash. Any mismatch — corrupted download, tampered module, a changed version — aborts the build.

This guards against supply-chain tampering: go.sum is committed to the repository, so everyone building the project validates the exact same dependency content. The interview answer: go.sum pins cryptographic hashes of module versions for integrity and reproducibility.

Answer:

It stores cryptographic checksums of dependency module versions, ensuring builds are reproducible and tamper-proof.

go.sum contains a hash (SHA-256 based) for every module version the project depends on — both direct and transitive. When go downloads a module, it verifies the content against the recorded hash. Any mismatch — corrupted download, tampered module, a changed version — aborts the build.

This guards against supply-chain tampering: go.sum is committed to the repository, so everyone building the project validates the exact same dependency content. The interview answer: go.sum pins cryptographic hashes of module versions for integrity and reproducibility.

39. What is the output of the following bitwise operation?

x := 5  // 0101
y := 3  // 0011
fmt.Println(x &^ y)

Output: 4.

&^ is Go’s bit clear (AND NOT) operator. x &^ y clears (zeroes) the bits in x wherever the corresponding bit in y is 1.

Binary:

  • x = 0101
  • y = 0011
  • x &^ y: bit 0: 1 cleared by 1 → 0; bit 1: 0 → 0; bit 2: 1, y bit 0 → keep 1; bit 3: 0 → 0. Result 0100 = 4.

Equivalently, x &^ y is x & ^y (x AND NOT y). The interview answer: 4.

Answer:

4.

&^ is Go’s bit clear (AND NOT) operator. x &^ y clears (zeroes) the bits in x wherever the corresponding bit in y is 1.

Binary:

  • x = 0101
  • y = 0011
  • x &^ y: bit 0: 1 cleared by 1 → 0; bit 1: 0 → 0; bit 2: 1, y bit 0 → keep 1; bit 3: 0 → 0. Result 0100 = 4.

Equivalently, x &^ y is x & ^y (x AND NOT y). The interview answer: 4.

40. Which standard library package is used to parse command-line flags in Go?

Answer: flag.

The standard library’s flag package parses command-line options:

var name = flag.String("name", "world", "your name")
flag.Parse()
fmt.Println("Hello", *name)

It provides typed flag definitions — flag.String, flag.Int, flag.Bool, etc. — and handles both -name value and --name=value styles, plus auto-generated usage text via flag.Usage. (os.Args gives raw arguments; third-party packages like pflag/cobra offer richer features, but the standard answer is flag.) The interview answer: flag.

Answer:

flag.

The standard library’s flag package parses command-line options:

var name = flag.String("name", "world", "your name")
flag.Parse()
fmt.Println("Hello", *name)

It provides typed flag definitions — flag.String, flag.Int, flag.Bool, etc. — and handles both -name value and --name=value styles, plus auto-generated usage text via flag.Usage. (os.Args gives raw arguments; third-party packages like pflag/cobra offer richer features, but the standard answer is flag.) The interview answer: flag.

My Private Notes

Notes are auto-saved locally to this device.