Go is a statically typed, compiled language with built-in concurrency primitives, often used for backend services and infrastructure tooling. Topics include types and interfaces, idiomatic error handling, goroutines and channels, mutexes, context cancellation, goroutine leaks and deadlocks, writing HTTP servers, testing, and performance considerations such as string and byte slice conversions.
All 75 Golang quiz questions below come with a short explanation, visible right on the page — no sign-in needed. They're grouped by beginner and intermediate levels and 34 subtopics, so you can revise one part at a time. Choose Try Golang quiz above to take a scored run and review the questions you got wrong.
Prints 3 then -3. Division between two ints is integer division; Go truncates the fractional part TOWARD ZERO (truncate toward zero), so 7/2 = 3 and -7/2 = -3 — unlike Python, which rounds down (-4). To get a float result, at least one operand must be a float.
How does a type satisfy (implement) an interface in Go?
A.You must register the type with the interface at runtime through a register function
B.Implicitly: having all the methods the interface requires is enough
C.You must declare it explicitly with an implements keyword as in Java
D.You must inherit (extend) from that interface
Answer: B
In Go, a type satisfies an interface implicitly: it just needs to define all the methods the interface requires, with no implements keyword or link declaration. The compiler checks this at compile time. This lets interfaces be defined on the consumer side, decoupled from the concrete type.
In Go, when should you return an error versus use panic/recover?
A.Always use panic instead of error because it is more concise
B.Return an error for expected failures in the normal flow
C.error is only for network failures; everything else must panic
D.recover can be called anywhere to swallow any runtime error
Answer: B
Go treats errors as values: a function returns error in the last position and the caller checks if err != nil. Use error for all expected failures (I/O, parse, validate). Only panic on a bug or a broken invariant you can't continue from; put recover in a defer to stop the panic at a boundary and avoid crashing the whole process.
What is the core difference between an array and a slice in Go?
A.An array has a fixed length that is part of its type; a slice is a dynamic view over a backing array
B.An array is heap-allocated while a slice always lives on the stack
C.A slice has a fixed length while an array can grow
D.Arrays and slices are identical, differing only in name
Answer: A
An array [N]T has its length N as part of the type and is copied by value on assignment or when passed to a function. A slice is a lightweight struct with a pointer to a backing array, len, and cap; it's a dynamic reference "window" that grows via append and shares data with other slices over the same backing array. In practice Go uses slices almost everywhere.
After declaring the following variables without assigning values, what do they contain?
go
var s stringvar n intvar p *int
A.All three are undefined until assigned
B.Each variable receives its type's zero value
C.They hold random garbage from memory, as in C
D.The program will not compile, because variables must be initialised
Answer: B
Every Go variable declared without initialization gets the zero value of its type: s = "", n = 0, p = nil. General rule: numbers → 0, string → empty string, bool → false, pointer/slice/map/channel/interface → nil, struct → the zero value of each field. There's no undefined or garbage value like in C.
How does the declaration x := 10 differ from var x = 10?
A.:= can only be used inside a function
B.A variable declared with := has no fixed type and can change type as in JavaScript
C.:= creates an unchangeable constant while var creates an ordinary variable
D.:= assigns to an existing variable while var is what declares a new one
Answer: A
x := 10 (short declaration) both declares and assigns, the type is inferred from the right-hand side, and it can ONLY be used inside a function. var x = 10 does the same but also works at package level. Inside a function, := is the most common form; var is used when declaring at package level or when you want the variable to get its zero value (var x int).
What determines whether an identifier (function, variable, struct, field) is exported — usable from another package — in Go?
A.The identifier must be listed in an export list in go.mod
B.A public / private keyword before the declaration, as in Java or C#
C.The first letter of the name: capitalised is exported (fmt.Println), lowercase is not
D.Every identifier is usable from another package; Go has no notion of private
Answer: C
The first letter of the name decides: uppercase is exported — other packages can import and use it (fmt.Println, http.Get); lowercase is unexported — only usable within the package. Go has no public/private keyword. The rule applies to every identifier: functions, variables, constants, types, and each field in a struct.
range copies each element — what does the following code print?
go
nums := []int{1, 2, 3}
for _, v := range nums {
v *= 10
}
fmt.Println(nums)
A.[10 2 3] — only the first element changes
B.[1 2 3] — v is a copy of each element, so modifying v does not affect the slice
C.A compile error, because v is assigned but the result is unused
D.[10 20 30] — v is a direct reference to the element in the slice
Answer: B
It prints [1 2 3]. range copies the VALUE of each element into the loop variable v — modifying v modifies the copy, so the original slice is unchanged. To change the actual elements, assign through the index: for i := range nums { nums[i] *= 10 }. This rule also applies to structs in a slice: modifying a field of v has no effect either.
Looking up a missing map key — what does this code print?
go
funcmain() {
m := map[string]int{"a": 1}
v, ok := m["b"]
fmt.Println(v, ok)
}
A.1 false
B.nil false
C.0 false
D.panic: key not found
Answer: C
Prints 0 false. Looking up a missing key on a map doesn't panic: the comma-ok form v, ok := m["b"] returns the zero value of the value type (int → 0) and ok == false. If you drop ok, you only get 0 — you can't tell a missing key from a key whose value is 0.
Which of the following is the correct way to use sync.WaitGroup when launching multiple goroutines?
A.Call wg.Add(1) as the first line INSIDE each goroutine, just before the real work
B.Call wg.Add(1) BEFORE the go statement, defer wg.Done() in each goroutine, and wg.Wait() in the waiting goroutine
C.Calling wg.Done() more times than Add is harmless, since the counter stops at 0
D.No Add is needed; wg.Wait() counts the running goroutines automatically
Answer: B
The correct pattern: wg.Add(1) BEFORE the go statement, defer wg.Done() inside the goroutine, and wg.Wait() on the waiting side. Calling Add inside the goroutine is a classic race — Wait can see a counter of 0 and return before the goroutine even runs. Done beyond Add panics with a negative counter.
int32. A rune represents one Unicode code point, which needs up to 21 bits, so it must use a 32-bit type. Meanwhile byte is an alias for uint8. These two types exist to make intent clear when working with strings.
What is the difference between len(s) and cap(s) of a slice?
A.One is how many elements it currently holds, the other the backing array's capacity
B.One counts non-zero elements while the other counts all of them
C.One counts elements while the other counts the bytes occupied
D.The two are always equal, so only one of them is needed
Answer: A
len is the number of elements the slice currently holds. cap is the number of elements the underlying array can hold starting from the slice's start position. When append exceeds capacity, the runtime allocates a new, larger array and copies the data over — at that point the old and new slices no longer share an underlying array.
What does the any (or interface{}) type mean in Go?
A.A dynamic type whose real type can change while the program runs
B.A special type allowing any method to be called on the value inside
C.A universal pointer able to address the memory of any type
D.An interface with no methods, so every type satisfies it
Answer: D
It is an interface that declares no methods. Since every type satisfies that empty condition, a variable of this type can hold any value. In return, you can't call any method on it until you pin down the concrete type via a type assertion or type switch.
In Go, what is wrong with discarding a returned error by assigning it to the blank identifier?
A.The garbage collector cannot release the resources attached to that error
B.The compiler refuses to compile that code
C.The program panics at the point where the error was discarded
D.The operation may have failed while the program carries on regardless
Answer: D
The operation may already have failed while the program keeps running as if nothing happened. The other return values are then usually zero or incomplete, so later code processes bad data and the real error surfaces elsewhere, far from the cause.
A.go.sum is only used for release builds, not during development
B.go.sum lists direct dependencies while go.mod lists indirect ones
C.go.sum is hand-written while go.mod is generated by tooling
D.go.sum holds hashes for verifying the contents of downloaded modules
Answer: D
go.mod declares the module and its dependencies with their versions. go.sum stores the cryptographic hash of each downloaded version, to verify the contents haven't been altered. Both must be committed to the repository.
What is the difference between errors.New("x") and fmt.Errorf("x")?
A.fmt.Errorf is markedly slower and should only be used when necessary
B.fmt.Errorf allows interpolating values and wrapping another error into the message
C.errors.New creates an error with a stack trace while fmt.Errorf does not
D.errors.New can only be used within the current package
Answer: B
errors.New creates an error from a fixed string. fmt.Errorf takes a format string, so it can interpolate values into the message and, more importantly, supports the %w verb to wrap another error — preserving the original for later errors.Is and errors.As checks.
A.The source file is called main.go at the project root
B.A module declaration in a configuration file in the same directory
C.The package is named main and has a main() function taking no parameters
D.The package imports no other package in the project
Answer: C
Two conditions together: the package must be named main, and it must have a func main() that takes no parameters and returns nothing. Missing either one means the build command only produces a library, not an executable.
A.In exactly the order they were declared in the function
B.Only the last one runs when the function ends
C.Undefined, decided by the scheduler
D.In the reverse order of declaration
Answer: D
In reverse declaration order — the last declared runs first. Deferred calls are pushed onto a stack, and this order matches releasing resources in the opposite order from opening them.
C.It does not fall through to the next case so no break is needed
D.It only works when the condition is a constant
Answer: C
It does not fall through to the next case, so no break statement is needed at the end of each case. Falling through must be declared explicitly — this design eliminates the whole class of forgotten-break bugs.
Reading from and writing to a map not created with make — what happens?
A.Reading returns the zero value while writing panics
B.Both work as they would on an ordinary map
C.Both reading and writing panic immediately
D.The map is initialized automatically on the first write
Answer: A
Reading returns the type's zero value, writing panics. An uninitialized map has the nil value, and the language allows reading from it like an empty map, but there is nowhere to write into it.
funcmain() {
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
}
A.3
3
3
B.0
1
2
C.(nothing is printed)
D.2
1
0
Answer: D
Prints 2 1 0. defer runs LIFO; the argument i is evaluated IMMEDIATELY when the defer is reached, so the three calls capture 0, 1, 2 and print in reverse when main returns.
funcmain() {
var m map[string]int
m["a"] = 1
fmt.Println(m)
}
A.map[a:1]
B.panic: index out of range [0] with length 0
C.panic: assignment to entry in nil map
D.panic: runtime error: invalid memory address
Answer: C
Panic: assignment to entry in nil map. var m map[string]int only creates a nil map; reading a nil map safely returns the zero value, but WRITING panics. You must make(map[string]int) or use a literal before assigning.
const (
A = iota
B
C
_
E
)
funcmain() {
fmt.Println(A, B, C, E)
}
A.0 1 2 0
B.1 2 3 5
C.0 1 2 4
D.0 1 2 3
Answer: C
Prints 0 1 2 4. iota starts at 0 and increments by 1 on each line of the const block, including the _ line. Lines B, C, E implicitly inherit the = iota expression. The _ line "swallows" the value 3, so E = 4.
funcmain() {
s := "héllo"
fmt.Println(len(s), utf8.RuneCountInString(s))
}
A.5 6
B.5 5
C.6 5
D.6 6
Answer: C
Prints 6 5. A string in Go is a UTF-8 byte sequence; len(s) counts BYTES. The character "é" takes 2 bytes, so the string is 6 bytes long but only 5 runes (characters). To count characters, use utf8.RuneCountInString or range.
Comparing structs with == — what does this code print?
go
type Point struct {
X, Y int
}
funcmain() {
a := Point{1, 2}
b := Point{1, 2}
fmt.Println(a == b)
}
A.false
B.compile error
C.true
D.panic
Answer: C
Prints true. A struct whose fields are all comparable (here all int) can be compared with ==, matching field by field by value. a and b are both {1, 2} so they're equal — no need for reflect.DeepEqual in this case.
What is the core difference between a goroutine and an OS thread?
A.A goroutine is a lightweight thread managed by the Go runtime, multiplexed onto a few OS threads
B.A goroutine is an OS thread, simply named differently in Go
C.A goroutine is scheduled directly by the OS, bypassing the Go runtime
D.A goroutine runs truly in parallel across CPUs while an OS thread does not
Answer: A
A goroutine is a lightweight thread managed by the Go runtime, not an OS thread. Go's M:N scheduler multiplexes many goroutines onto a small number of OS threads. A goroutine stack starts at only about 2KB and grows and shrinks automatically, while an OS thread costs hundreds of KB up to a few MB, so creating thousands of goroutines is normal.
What is the difference between an unbuffered and a buffered channel when sending/receiving?
A.An unbuffered channel is faster because it skips the synchronisation step
B.Only a buffered channel can be closed with close()
C.A buffered channel never blocks, whether the buffer is full or empty
D.An unbuffered channel is synchronous (a send blocks until a receiver is ready)
Answer: D
An unbuffered channel (make(chan T)) is synchronous: each send blocks until a receiver is there, creating a handshake point between the two goroutines. A buffered channel (make(chan T, n)) has capacity n: a send blocks only when the buffer is full, a receive blocks only when the buffer is empty, which decouples the pace of producer and consumer.
A.It iterates over the channels in the order the cases are written
B.It closes every channel listed in the cases
C.Waits on multiple channel operations and executes a ready case
D.It picks a branch based on an expression's value, like an ordinary switch
Answer: C
select lets a goroutine wait on multiple channel operations at once and run one case that is ready. If several cases are ready at the same time, Go picks randomly to avoid starvation. With default, select is non-blocking. It's the core tool for timeouts, cancellation and fan-in/fan-out.
A.Carrying cancellation signals, deadlines and request-scoped values
B.Storing long-lived application state in place of global variables
C.Replacing channels for every goroutine synchronisation problem
D.Automatically limiting the memory a goroutine may allocate
Answer: A
context.Context carries cancellation signals, deadlines/timeouts, and a small amount of request-scoped values across a call chain and goroutines. A function that takes ctx listens on ctx.Done() to stop early when the request is cancelled or times out. Convention: pass ctx as the first parameter, don't store it in a struct, and don't use it as a parameter bag.
How do sync.WaitGroup and sync.Mutex differ in purpose?
A.Both are mutual-exclusion locks, differing only in name
B.WaitGroup protects shared variables and Mutex waits for goroutines to finish
C.WaitGroup can replace Mutex in every case
D.WaitGroup waits for a group of goroutines to finish
Answer: D
sync.WaitGroup synchronizes on a completion point: Add(n) before starting, each goroutine does defer wg.Done(), and wg.Wait() blocks until the counter hits 0 — used to wait for a whole group of goroutines to finish. sync.Mutex is a mutual-exclusion lock: Lock/Unlock wrap a critical section so only one goroutine touches shared state at a time. Different purposes, often used together.
C.fatal error: all goroutines are asleep - deadlock!
D.1 1
Answer: B
Prints 1 2. A channel with buffer 2 lets you send two values without a ready receiver — sends only block when the buffer is FULL. The buffer is a FIFO queue, so receives come back in send order. If you change it to make(chan int) (unbuffered), the line ch <- 1 blocks forever in the single goroutine → deadlock.
Prints 2 [1 2]. copy(dst, src) copies min(len(dst), len(src)) elements and returns the number copied — here min(2, 3) = 2. copy does NOT grow dst; the classic trap is dst := make([]int, 0, n) (len 0), which makes copy copy 0 elements even though cap has room.
The following function returns a nil *MyErr, yet the caller's err != nil check is true. Why?
go
funcdoWork()error {
var e *MyErr // nil pointer// ... no error happenedreturn e
}
err := doWork()
fmt.Println(err != nil) // true!
A.An interface value is a (type, value) pair; return e makes an interface with type *MyErr and a nil value
B.This is a Go compiler bug, fixed in newer versions
C.Because err != nil compares the address of the variable err, which is never nil
D.Because error is a special interface that is never nil once assigned through a return
Answer: A
An interface value is a (type, value) pair and is only nil when BOTH are nil. return e where e is a nil *MyErr creates the interface (type=*MyErr, value=nil) — the type isn't nil, so err != nil is true. This is the "typed nil" trap. To avoid it: a function returning error should return nil explicitly, not return a nil error pointer variable.
Why use strings.Builder when concatenating strings in a loop?
A.Because string concatenation does not compile inside a loop
B.Because strings are immutable, so each concatenation allocates and copies again
C.Because the builder is safe when several goroutines write to it at once
D.Because the builder inserts a separator between the parts written into it
Answer: B
Since Go strings are immutable, s += x allocates a new string and copies all the old content each time. Concatenating n times has quadratic cost. strings.Builder writes into a byte buffer that grows exponentially and returns the string once, so the total cost is linear.
How does v, ok := i.(MyType) differ from v := i.(MyType)?
A.The two-value form converts the type while the single-value form only checks it
B.The two-value form only works with the empty interface
C.The two-value form does not panic when the type does not match
D.The two-value form copies the value while the single-value form returns a pointer
Answer: C
The two-value form is safe: if the type doesn't match, v gets the zero value and ok is false, and the program continues. The one-value form panics immediately when the type doesn't match. In production code you almost always use the two-value form.
A.Declaring a constant whose type is resolved at runtime rather than compile time
B.Producing a unique random value for each constant in the block
C.Marking a constant as usable only within the current package
D.Generating an ascending integer sequence by declaration line
Answer: D
It generates a sequence of increasing integers: it takes the value 0 on the first line of the const block and increments by one on each subsequent line. This is how Go declares enums without manual numbering, so inserting a value in the middle doesn't require changing the lines after it.
Which approach creates an independent copy of the slice src?
A.dst := src, then treating dst as a separate slice
B.dst := new([]T) and then assigning each element of the original
C.dst := make([]T, len(src)); copy(dst, src)
D.dst := src[:] to create a new slice from the whole original
Answer: C
make a new slice of the right length, then call copy. Both assignment and slicing only copy the descriptor (pointer, length, capacity), not the data, so the two slices still share the same underlying array and a change to one is visible in the other.
What happens when several goroutines read and write a plain map concurrently?
A.The runtime locks the map so the goroutines queue up behind each other
B.The data is corrupted but the program carries on as normal
C.There is no problem, because Go maps are thread-safe by design
D.The runtime detects it and panics
Answer: D
The program can stop with a fatal error reporting concurrent map access, e.g. concurrent map read and map write or concurrent map writes. This is intentional runtime behavior in many cases, but you shouldn't rely on it as a protection mechanism; maps generally still need synchronization when goroutines write to them.
What ordering do you get from two range loops over the same map?
A.Random, and possibly different between the two traversals
B.Random on the first pass but fixed on subsequent ones
C.The order in which the keys were inserted
D.Ascending key order when the keys are numbers or strings
Answer: A
Random, and two consecutive iterations can produce different orders. Go deliberately randomizes the starting point rather than merely not guaranteeing an order — the goal is to immediately expose code that accidentally depends on iteration order.
What does embedding one struct inside another give you?
A.The two structs share memory, so changing one changes the other
B.The outer struct inherits from the inner one and can override its methods
C.The outer struct automatically satisfies every interface the inner one declares
D.The inner struct's fields and methods become directly accessible from outside
Answer: D
The embedded struct's fields and methods are promoted to the outer struct, so you access them directly as outer.Field instead of outer.Inner.Field. This is composition, not inheritance: there is no polymorphism, and a same-named method on the outer struct only shadows rather than overrides.
A type T has a method with a pointer receiver. Does a value of type T (not *T) satisfy an interface containing that method?
A.It depends on whether the struct contains any pointer fields
B.No, only *T satisfies it
C.Yes, because Go takes the address of the value automatically when needed
D.Yes, provided the value is assigned to a variable before being passed
Answer: B
No. The method set of T includes only methods with value receivers. The method set of *T includes both. So only *T satisfies that interface, and assigning a T value to an interface variable is a compile error.
What is the difference in purpose between errors.Is and errors.As?
A.One returns a boolean while the other returns the original error in the chain
B.One checks only the outermost error while the other descends the wrap chain
C.One is for built-in errors and the other for custom ones
D.One compares against a specific error value, the other extracts an error by type
Answer: D
errors.Is(err, target) asks whether anything in the error chain equals target exactly — used with sentinel errors. errors.As(err, &target) finds the first error matching a type and assigns it to the variable, so you can read that error type's own fields.
What do you get when receiving from a closed channel?
A.It blocks forever because no new value will ever be sent
B.It returns the zero value immediately without blocking
C.It panics because the channel accepts no further operations
D.It returns an error indicating the channel was closed
Answer: B
Receiving returns immediately with the zero value of the element type, without blocking — and keeps doing so forever. Use the two-value form v, ok := <-ch to distinguish: ok being false means the channel is closed and drained.
A.The function runs before any other goroutine starts
B.The function passed in runs exactly once, however many goroutines call it
C.Only one goroutine may enter the code region at a time
D.The function runs once per goroutine that calls it
Answer: B
The passed function runs exactly once over the whole program lifetime, no matter how many goroutines call it. More importantly, later callers wait until that first run completes, so nobody observes a half-initialized state.
How is the worker pool pattern typically built in Go?
A.Creating a new goroutine per job and capping them with a mutex
B.One coordinating goroutine dividing the work evenly among the workers
C.N goroutines all reading from one shared jobs channel
D.An array of goroutines with jobs indexed by modulo
Answer: C
Start N goroutines that all range over a shared jobs channel. Each value sent to the channel is received by exactly one goroutine, so distribution is automatic and self-balancing — whichever worker is idle picks up the next job.
Why must you call defer cancel() after creating a context with context.WithTimeout?
A.To tell child goroutines the work completed successfully
B.To release the resources tied to the context as soon as it is no longer needed
C.To ensure the timeout is measured from the moment the function is called
D.To avoid a panic when the context times out without having been cancelled
Answer: B
To release resources. The child context is registered with its parent and a timer is created; if the work finishes early without calling cancel, both linger until the timeout expires. For a function called many times, that is a real leak.
A.The maximum number of OS threads running Go code simultaneously
B.The number of concurrent network connections the runtime can handle
C.The number of CPU cores the OS grants the Go process
D.The maximum number of goroutines the program may create
Answer: A
The maximum number of OS threads allowed to execute Go code simultaneously — that is, the real level of parallelism. It does not limit the number of goroutines, which can go up to millions. Since Go 1.5, the default value equals the number of CPU cores the runtime sees.
B.The maximum number of goroutines the garbage collector may use
C.The maximum heap size the program may allocate
D.The heap growth threshold before the next collection cycle runs
Answer: D
The heap growth ratio that triggers the next collection cycle. The default is 100, meaning it runs again when the heap reaches twice the live size from the previous collection. Setting it higher means less frequent collection but more RAM usage; setting it lower is the opposite.
A.Adding a new test case means adding one line to the table
B.The test runner stops at the first failing case, saving time
C.The cases run in parallel so the test suite is faster
D.Tooling generates extra edge cases from the declared list
Answer: A
All cases share the same checking logic, so adding a new case is just adding a line of data. Combined with t.Run so each case becomes a named subtest, the report points out exactly which case failed and the remaining cases still run.
In a benchmark function, what is the purpose of the for i := 0; i < b.N; i++ loop?
A.The number of times the benchmark repeats to average the final result
B.A fixed iteration count set by the programmer in the source
C.The number of CPU cores available to run the benchmark in parallel
D.The runner raises the iteration count itself until the measurement is reliable
Answer: D
The benchmark runner decides this value itself: it starts with a small number of iterations, measures, then increases until the total time is long enough (about one second by default) for a stable result. The reported result is the average time per iteration.
C.5 seconds to connect and unlimited for reading the data
D.30 seconds, enough for most ordinary requests
Answer: A
None. http.DefaultClient leaves the timeout field at its zero value, meaning it waits forever. If the target server stops responding without closing the connection, the calling goroutine hangs indefinitely — and in a service, this accumulates into resource exhaustion.
Why must you call defer resp.Body.Close() after every HTTP request?
A.To return the connection to the pool; otherwise it is held and leaks
B.To free the cache holding the response content already read
C.To ensure all the response data has been written to its destination
D.To tell the server the client has everything and it may close the session
Answer: A
To return the TCP connection to the pool for reuse. If you don't close it, the connection is held forever, and after enough requests the process runs out of file descriptors. Call it right after checking the error, before reading the body.
What must you do with a time.Ticker when you are done with it?
A.Set the variable to nil so the runtime recognises it and frees it
B.Call Stop(), or the timer keeps running and leaks resources
C.Nothing — the garbage collector cleans it up once no references remain
D.Close its channel to tell receivers it has finished
Answer: B
Call Stop() when you no longer need ticks, usually with a defer right after creating it if the ticker's lifetime matches the current scope. Since Go 1.23, the GC can reclaim an unreferenced ticker even without Stop(), but Stop() is still the correct way to stop one explicitly.
Which Go version added generics, and what problem do they solve?
A.Go 1.18 — writing one function for many types while keeping compile-time checking
B.Go 1.18 — allowing one type to satisfy several interfaces at once
C.Go 1.20 — allowing implicit conversion between compatible numeric types
D.Go 1.21 — allowing a function to take a variable number of parameters
Answer: A
Go 1.18. They let you write a single function or type shared across many data types while still being checked at compile time. Before that you had only two options: duplicate the code for each type, or use interface{} with type assertions and lose type safety.
What happens when a goroutine panics without recovering?
A.The whole program stops, not only that goroutine
B.The runtime restarts that goroutine from the beginning
C.Only that goroutine ends and the others carry on as normal
D.The panic becomes an error returned to the goroutine that created it
Answer: A
The whole program crashes. The panic unwinds the goroutine's stack and, if it reaches the top without being recovered, the runtime terminates the entire process. Go does not isolate errors per goroutine.
A.Collect errors and cancel shared context on first error
B.Limiting how many goroutines in the group run concurrently to a fixed number
C.Guaranteeing the goroutines run in the order they were added to the group
D.Automatically retrying goroutines that ended with an error
Answer: A
It collects errors from the goroutines. Wait() returns the first error encountered instead of nothing, and the version built with errgroup.WithContext cancels the shared context as soon as the first goroutine fails — so remaining goroutines can stop early if they respect the context.
A.Yes, every deferred statement runs before the process ends
B.No, it terminates the process immediately
C.It only runs the defers in main and skips the other functions
D.Yes when the exit code is non-zero, no when it is zero
Answer: B
No. It terminates the process immediately, skipping all pending defers. The consequence is unclosed files, unflushed buffers, and any other cleanup steps never running.
What happens when := inside an if block reuses a name from an outer scope?
A.It assigns straight into the outer variable like an ordinary assignment
B.The outer variable is removed from scope once the block ends
C.It creates a new variable shadowing the outer one, so assigning to it leaves the outer untouched
D.A compile error, because the name already exists in the enclosing scope
Answer: C
It creates a new variable that shadows the outer one within that block. Assignments inside affect the new variable, while the outer variable keeps its old value after the block ends. This is the source of bugs that look like assignments do nothing.
What does the Go guideline "accept interfaces, return structs" mean?
A.A function should not return an interface because it cannot be serialised
B.Parameters should be interfaces for flexibility while return values should be concrete types
C.Structs should be passed by pointer and interfaces by value
D.Every exported function must declare an accompanying interface
Answer: B
Parameters should be declared as interface types so callers can pass any implementation — easy to swap in tests. Return values should be concrete types so callers get the full method set and decide for themselves whether they need abstraction.
What must a Go module at v2 or above do with its module path?
A.Keep the path unchanged and mark the version with a tag alone
B.Append the version suffix to the end of the module path
C.Create a separate branch in the repository and point the configuration file at it
D.Register the new path with the module proxy before publishing
Answer: B
Append the version suffix to the end of the path: example.com/lib/v2. Because the paths differ, Go treats the two major versions as completely separate modules — which lets a single build contain both v1 and v2 when dependencies require different ones.
A.One is for library code and the other for executable programs
B.One runs at compile time and the other while the program executes
C.One checks the tests and the other the business code
D.One finds suspicious code, the other standardises formatting
Answer: D
gofmt normalizes formatting — indentation, whitespace, import order — and doesn't care about semantics. go vet does semantic analysis to find patterns that compile but are almost certainly bugs, for example a format string that doesn't match its arguments.
D.Yes, because the two slices share the same backing array
Answer: D
Yes. Slicing does not copy the data but creates a new view onto the same underlying array, so writing through this slice is visible to the other. To make them independent you must copy explicitly.
Reading a 100MB file and keeping data[:10] as the result. What is the problem?
A.The sub-slice becomes invalid once the file is closed
B.Accessing the sub-slice is slower because indices must be recomputed
C.The whole 100MB backing array is not released
D.The elements outside the slice range are set to their zero value
Answer: C
The entire 100MB underlying array is not freed. The sub-slice still references it, so the GC cannot reclaim the rest even though the program only uses the first ten bytes.
On Go 1.22 and earlier, using time.After in a select inside an infinite loop causes what?
A.Each iteration creates a new timer that is not yet released
B.The loop is blocked until the first timer expires
C.The select statement always prefers the timer branch
D.The old timers keep firing and cause the wrong signal to be received
Answer: A
Each loop iteration creates a new timer, and up to Go 1.22 a timer is only reclaimed after it fires. With a fast loop and a long wait, the number of pending timers in memory accumulates very quickly. Since Go 1.23 (a module declaring go 1.23 or later), unexpired timers are garbage-collected too, so the problem all but disappears.
What important configuration does a default http.ListenAndServe lack?
A.Read and write timeouts for each connection
B.A logger for the handled requests
C.A certificate for encrypted connections
D.A limit on the number of concurrent connections accepted
Answer: A
The read and write timeout settings. By default there are no limits, so a slow or deliberately connection-holding client can occupy resources indefinitely — and this is a real attack.
Unmarshalling JSON leaves one struct field empty. Most common cause?
A.The input JSON has extra fields not declared in the struct
B.The field's type does not match the type in the JSON
C.The field name is lowercase so it is not exported
D.The struct was not passed in as a pointer
Answer: C
The field name is lowercase and therefore not exported. The encoding package uses reflection and can only access exported fields, so it skips it entirely — no error is returned.
B.When goroutines must wait for each other at a rendezvous point
C.When several different variables must be protected at once
D.When reads greatly outnumber writes
Answer: D
When reads vastly outnumber writes. Many concurrent readers can proceed in parallel, so throughput increases. If the write ratio is high, the benefit does not offset the extra management cost of this lock type.
A.The receiver, because it knows when it needs no more data
B.The goroutine that created the channel, whatever its role
C.Nobody, since a channel is reclaimed automatically
D.The sender, because only it knows when the data ends
Answer: D
The sender, because only it knows when the data is done. Sending on a closed channel causes a panic, so letting the receiver close it invites an unexpected program stop.
Placing defer f.Close() inside a loop over 10k files causes what?
A.A deferred statement inside a loop is rejected by the compiler
B.Every file stays open until the function ends, exhausting the file descriptors
C.Only the last file is closed and the earlier ones are skipped
D.The deferred statements run in the wrong order relative to the opens
Answer: B
Every file stays open until the function returns, because a deferred call is tied to the function, not the loop. With ten thousand files, the program runs out of file descriptors before the function ends.
A.Each time the package is imported from a new place
B.Right after main, once the program is ready
C.The first time one of the package's functions is called
D.After the package-level variables are initialized and before main
Answer: D
After package-level variables are initialized and before main runs. Each package runs exactly once, in dependency order: imported packages finish initializing before the current one starts.
How does comparing two structs with == work in Go?
A.It compares the two structs' memory addresses in the heap
B.It compares field by field, but only when every field is comparable
C.It only compares the fields exported outside the package
D.It always returns false unless both variables are the same object
Answer: B
It compares every field by value, but this is only valid when all fields are comparable. A struct containing a slice, map or function is rejected at compile time, not at runtime.
How do strings.Split(s, " ") and strings.Fields(s) differ on multiple spaces?
A.One preserves the element order and the other sorts them
B.One produces empty elements while the other ignores the extra whitespace
C.One returns a slice and the other a fixed-size array
D.One handles multi-byte characters and the other does not
Answer: B
The separator-based split produces empty elements when two spaces appear in a row, while the other one groups all whitespace together and drops the extras. For user-supplied text, the second is almost always what you want.
B.The required parameters a function needs to do its work
C.Application-wide configuration such as the database address
D.Request-scoped data such as a trace id or an identity
Answer: D
Only data scoped to a request, such as a trace ID, the caller's identity or locale info. Do not use it for required arguments or dependencies — those belong in the function signature or in initialization-time injection.
A bufio.Scanner stops mid-file without a clear error. Why?
A.It hit a line longer than the default buffer limit
B.The file contains multi-byte characters the scanner cannot handle
C.The scanner can only read a fixed maximum number of lines
D.Another process locked the file during reading
Answer: A
It hit a line longer than the default buffer limit. The scan loop stops as if the file had ended, and the error only surfaces when you check the error function after the loop — a step that is very often skipped.
A second go test prints (cached) without rerunning. Why?
A.Caching was explicitly enabled in the project's configuration
B.The tests ran too fast so they are skipped next time
C.The result is cached when the code and inputs are unchanged
D.The tests contain no assertion calls to check
Answer: C
The result was cached because the source and its input factors were unchanged. To force a rerun, add the flag that sets the count to one, or clear the build cache.