1. What is the default value (zero value) of an uninitialized slice in Go?
Answer: nil.
Every variable in Go starts at its zero value, and each type has a defined one: 0 for numbers, false for booleans, empty string for strings, nil for pointers, maps, functions, interfaces — and slices.
A nil slice is a perfectly valid slice header: its length is 0, its capacity is 0, and it points to no underlying array. In practice, a nil slice behaves like an empty slice for almost everything you do — ranging over it yields nothing, and append on a nil slice works fine (it allocates the backing array on first use).
The distinction worth knowing: nil slice vs. empty slice. var s []int gives you a nil slice. make([]int, 0) (or []int{}) gives you an empty, non-nil slice with a zero-length but potentially allocated backing array. For equality checks in code, len(s) == 0 is the correct test — it’s true for both.
The interview answer: the zero value of a slice is nil, which has length 0 and capacity 0 and points to no underlying array.
Answer:
nil.
Every variable in Go starts at its zero value, and each type has a defined one: 0 for numbers, false for booleans, empty string for strings, nil for pointers, maps, functions, interfaces — and slices.
A nil slice is a perfectly valid slice header: its length is 0, its capacity is 0, and it points to no underlying array. In practice, a nil slice behaves like an empty slice for almost everything you do — ranging over it yields nothing, and append on a nil slice works fine (it allocates the backing array on first use).
The distinction worth knowing: nil slice vs. empty slice. var s []int gives you a nil slice. make([]int, 0) (or []int{}) gives you an empty, non-nil slice with a zero-length but potentially allocated backing array. For equality checks in code, len(s) == 0 is the correct test — it’s true for both.
The interview answer: the zero value of a slice is nil, which has length 0 and capacity 0 and points to no underlying array.
2. What will be the output of the following slice modification?
a := []int{1, 2, 3}
b := a[1:3]
b[0] = 99
fmt.Println(a)
Output: [1 99 3]
The trap is thinking a slice is its own copy of the data. It isn’t.
A slice is a header — three fields: a pointer to an element of an underlying array, a length, and a capacity. Slicing a[1:3] creates a new header (so b has length 2, pointing at a’s element 1), but it points into the same underlying array that a uses.
So b[0] is actually the same memory slot as a[1]. Writing b[0] = 99 changes the shared array element, and a sees it: a becomes [1, 99, 3].
The output is [1 99 3].
This is the fundamental slice gotcha. It’s exactly why the copy idiom exists: if you need an independent array, use copy(b, a[1:3]) with a freshly allocated slice, or append([]int(nil), a[1:3]...). Otherwise, remember that slices are views, and writes through one view show up in all the others.
Answer:
[1 99 3]
The trap is thinking a slice is its own copy of the data. It isn’t.
A slice is a header — three fields: a pointer to an element of an underlying array, a length, and a capacity. Slicing a[1:3] creates a new header (so b has length 2, pointing at a’s element 1), but it points into the same underlying array that a uses.
So b[0] is actually the same memory slot as a[1]. Writing b[0] = 99 changes the shared array element, and a sees it: a becomes [1, 99, 3].
The output is [1 99 3].
This is the fundamental slice gotcha. It’s exactly why the copy idiom exists: if you need an independent array, use copy(b, a[1:3]) with a freshly allocated slice, or append([]int(nil), a[1:3]...). Otherwise, remember that slices are views, and writes through one view show up in all the others.
3. What is the result of using append() when a slice’s capacity is exceeded?
Answer: Go allocates a new, larger underlying array, copies the existing elements over, and returns a new slice header pointing at the new storage.
append works on the slice’s capacity, not just its length. When there’s room — len < cap — the element is written into the existing backing array and len increments. Fast, no allocation.
When the slice is full (len == cap), append must grow. It allocates a brand-new backing array — the new capacity is typically about double the old one for small slices, tapering toward a smaller growth factor (often 1.25×) for very large ones — copies all existing elements across, and writes the new element. The returned slice header points to the new array.
The critical caveat that follows: because the new slice may point to a different array, you must always capture the return value — s = append(s, x). Using append(s, x) without reassignment can silently write to the new array while the old variable still references the old one.
The interview answer: append grows the slice by allocating a larger backing array, copying elements over, and returning the updated slice header.
Answer:
Go allocates a new, larger underlying array, copies the existing elements over, and returns a new slice header pointing at the new storage.
append works on the slice’s capacity, not just its length. When there’s room — len < cap — the element is written into the existing backing array and len increments. Fast, no allocation.
When the slice is full (len == cap), append must grow. It allocates a brand-new backing array — the new capacity is typically about double the old one for small slices, tapering toward a smaller growth factor (often 1.25×) for very large ones — copies all existing elements across, and writes the new element. The returned slice header points to the new array.
The critical caveat that follows: because the new slice may point to a different array, you must always capture the return value — s = append(s, x). Using append(s, x) without reassignment can silently write to the new array while the old variable still references the old one.
The interview answer: append grows the slice by allocating a larger backing array, copying elements over, and returning the updated slice header.
4. What is the time complexity of looking up a key in a Go map?
Answer: O(1) average, O(n) worst-case.
A Go map is a hash table built from buckets. To look up a key, Go computes the key’s hash, uses part of the hash to select a bucket, then compares the key against the entries in that bucket (with a small per-bucket lookup structure for speed). When keys hash and distribute well, this is constant time — O(1) on average.
The O(n) worst case arises from hash collisions: if many keys land in the same bucket, lookups degrade to scanning within that bucket, which can grow to O(n) in pathological cases (e.g., adversarial keys that all hash identically, or a badly-behaved hash function). In practice, Go’s randomized per-map hash seed and bucket overflow handling keep this from being a real problem.
The interview answer: O(1) average for map lookups, degrading to O(n) worst-case under severe collisions — standard hash-table behavior.
Answer:
O(1) average, O(n) worst-case.
A Go map is a hash table built from buckets. To look up a key, Go computes the key’s hash, uses part of the hash to select a bucket, then compares the key against the entries in that bucket (with a small per-bucket lookup structure for speed). When keys hash and distribute well, this is constant time — O(1) on average.
The O(n) worst case arises from hash collisions: if many keys land in the same bucket, lookups degrade to scanning within that bucket, which can grow to O(n) in pathological cases (e.g., adversarial keys that all hash identically, or a badly-behaved hash function). In practice, Go’s randomized per-map hash seed and bucket overflow handling keep this from being a real problem.
The interview answer: O(1) average for map lookups, degrading to O(n) worst-case under severe collisions — standard hash-table behavior.
5. What will fmt.Println(a == b) produce for these slice declarations?
var a []int
b := []int{}
Answer: Compilation error — slices are not comparable with == (except against nil).
A slice is a descriptor (pointer + length + capacity), and Go forbids comparing two slices with ==. There’s no meaningful value-equality for slices: should a == b compare lengths? Contents? Underlying arrays? Go sidesteps the ambiguity by making the comparison a compile-time error.
The one exception: a slice can be compared to nil — a == nil — which tests whether the slice header has no backing array. (Here a is nil and b is an empty non-nil slice, but you can’t even ask.)
If you need to compare slice contents, you use the explicit tool: slices.Equal(a, b) (Go 1.21+) or reflect.DeepEqual(a, b).
The interview answer: a compile error — slices aren’t ==-comparable, only nil-comparable.
Answer:
Compilation error — slices are not comparable with == (except against nil).
A slice is a descriptor (pointer + length + capacity), and Go forbids comparing two slices with ==. There’s no meaningful value-equality for slices: should a == b compare lengths? Contents? Underlying arrays? Go sidesteps the ambiguity by making the comparison a compile-time error.
The one exception: a slice can be compared to nil — a == nil — which tests whether the slice header has no backing array. (Here a is nil and b is an empty non-nil slice, but you can’t even ask.)
If you need to compare slice contents, you use the explicit tool: slices.Equal(a, b) (Go 1.21+) or reflect.DeepEqual(a, b).
The interview answer: a compile error — slices aren’t ==-comparable, only nil-comparable.
6. How can you safely check if a key exists in a map without triggering a zero-value fallback?
Answer: Use the comma-ok idiom: if val, ok := m["key"]; ok { ... }.
Reading m["key"] alone is ambiguous: a missing key and a key mapped to the zero value both return the zero value, so you can’t tell which happened.
The two-value form resolves it:
val, ok := m["key"]
ok == true— the key exists;valis its value.ok == false— the key is absent;valis the zero value.
This is the standard, safe way to distinguish “present with value 0” from “not present at all.” The interview answer: the val, ok := m[key]; ok idiom.
Answer:
Use the comma-ok idiom: if val, ok := m["key"]; ok { ... }.
Reading m["key"] alone is ambiguous: a missing key and a key mapped to the zero value both return the zero value, so you can’t tell which happened.
The two-value form resolves it:
val, ok := m["key"]
ok == true— the key exists;valis its value.ok == false— the key is absent;valis the zero value.
This is the standard, safe way to distinguish “present with value 0” from “not present at all.” The interview answer: the val, ok := m[key]; ok idiom.
7. What is the default capacity of a slice created as make([]int, 5)?
Answer: 5.
make([]int, length) — with a single size argument — creates a slice where the capacity defaults to the length. So make([]int, 5) gives a slice with length 5 and capacity 5, all elements zeroed.
If you want a different capacity, pass it explicitly: make([]int, 5, 10) creates a slice with length 5 but capacity 10 — a backing array holding 10 slots, with only the first 5 as the visible length. The extra capacity is headroom so appends don’t allocate immediately.
The interview answer: capacity equals the length (5) when only a length argument is given.
Answer:
5.
make([]int, length) — with a single size argument — creates a slice where the capacity defaults to the length. So make([]int, 5) gives a slice with length 5 and capacity 5, all elements zeroed.
If you want a different capacity, pass it explicitly: make([]int, 5, 10) creates a slice with length 5 but capacity 10 — a backing array holding 10 slots, with only the first 5 as the visible length. The extra capacity is headroom so appends don’t allocate immediately.
The interview answer: capacity equals the length (5) when only a length argument is given.
8. What happens when calling append() on a nil slice?
Answer: It works fine — append allocates a new underlying array and returns a valid, initialized slice.
nil is a legitimate slice value (len 0, cap 0, no backing array), and append is designed to accept it. Since there’s no capacity, the first append allocates a fresh backing array, copies nothing (there’s nothing to copy), writes the new element, and returns a slice header pointing at the new storage.
var s []int // nil
s = append(s, 1) // s is now [1], backed by a real array
No panic, no nil-pointer dereference. This is why the “append to a nil slice” pattern is idiomatic for building a slice incrementally.
The interview answer: append on a nil slice allocates the backing array and returns a valid slice.
Answer:
It works fine — append allocates a new underlying array and returns a valid, initialized slice.
nil is a legitimate slice value (len 0, cap 0, no backing array), and append is designed to accept it. Since there’s no capacity, the first append allocates a fresh backing array, copies nothing (there’s nothing to copy), writes the new element, and returns a slice header pointing at the new storage.
var s []int // nil
s = append(s, 1) // s is now [1], backed by a real array
No panic, no nil-pointer dereference. This is why the “append to a nil slice” pattern is idiomatic for building a slice incrementally.
The interview answer: append on a nil slice allocates the backing array and returns a valid slice.
9. What is the result of using copy(dst, src) when dst is an empty slice ([]int{})?
Answer: 0 elements are copied — nothing happens.
copy(dst, src) copies at most min(len(dst), len(src)) elements. The destination’s length is the hard limit; copy never grows dst.
With dst := []int{} (length 0), the minimum is 0 regardless of src’s length, so zero elements are copied. dst stays empty.
This is a classic trap for people who expect copy to behave like append and resize the destination. It doesn’t — you must allocate dst with sufficient length first:
dst := make([]int, len(src)) // correct
copy(dst, src)
The interview answer: 0 elements are copied, because copy is bounded by the destination’s length.
Answer:
0 elements are copied — nothing happens.
copy(dst, src) copies at most min(len(dst), len(src)) elements. The destination’s length is the hard limit; copy never grows dst.
With dst := []int{} (length 0), the minimum is 0 regardless of src’s length, so zero elements are copied. dst stays empty.
This is a classic trap for people who expect copy to behave like append and resize the destination. It doesn’t — you must allocate dst with sufficient length first:
dst := make([]int, len(src)) // correct
copy(dst, src)
The interview answer: 0 elements are copied, because copy is bounded by the destination’s length.
10. What will fmt.Println(m == nil) output after running this code?
var m map[string]int
fmt.Println(m == nil)
Output: true.
A var m map[string]int declaration without initialization creates a nil map — its zero value. Unlike slices (which can’t be compared except to nil), maps can be compared to nil, and here the answer is true.
What you can do safely with a nil map:
len(m)→0.- Reading a key
m["k"]→ the zero value (withok == false).
What panics: writing to a nil map — m["k"] = 1 → panic: assignment to entry in nil map (the next question).
The interview answer: true — an uninitialized map is nil.
Answer:
true.
A var m map[string]int declaration without initialization creates a nil map — its zero value. Unlike slices (which can’t be compared except to nil), maps can be compared to nil, and here the answer is true.
What you can do safely with a nil map:
len(m)→0.- Reading a key
m["k"]→ the zero value (withok == false).
What panics: writing to a nil map — m["k"] = 1 → panic: assignment to entry in nil map (the next question).
The interview answer: true — an uninitialized map is nil.
11. What happens when you attempt to write a key to a nil map in Go?
Answer: A runtime panic: panic: assignment to entry in nil map.
A nil map has no internal storage to hold an entry. Writing to it can’t succeed, and Go panics rather than guessing. This is unrecoverable unless caught — and the standard guidance is to never write to a nil map.
The rule to remember, stated as a pair:
- Reading from a nil map is safe (returns zero values).
- Writing to a nil map panics.
The fix is initialization before first write: m = make(map[string]int) or m := map[string]int{}. This is the single most common map bug in Go. The interview answer: writing to a nil map panics with assignment to entry in nil map.
Answer:
A runtime panic: panic: assignment to entry in nil map.
A nil map has no internal storage to hold an entry. Writing to it can’t succeed, and Go panics rather than guessing. This is unrecoverable unless caught — and the standard guidance is to never write to a nil map.
The rule to remember, stated as a pair:
- Reading from a nil map is safe (returns zero values).
- Writing to a nil map panics.
The fix is initialization before first write: m = make(map[string]int) or m := map[string]int{}. This is the single most common map bug in Go. The interview answer: writing to a nil map panics with assignment to entry in nil map.
12. What is the result of applying append() to a slice without reassigning the return value (append(s, 1))?
Answer: A compile error — Go requires the return value of append to be used.
append may reallocate: when the slice is full, it returns a header pointing at a brand-new, larger backing array. The original s header still points at the old array, which may or may not have received the element. To make the result usable, you must capture it: s = append(s, 1).
Go enforces this discipline at compile time. If you write append(s, 1) as a bare statement and ignore the result, the compiler rejects it with something like append result not used. (In Go 1.22+, the diagnostic mentions the assignment explicitly.)
This is deliberate: an ignored append is almost certainly a bug, because the caller has no reliable way to know whether the element actually landed. The interview answer: a compile error — append’s result must be captured with s = append(s, x).
Answer:
A compile error — Go requires the return value of append to be used.
append may reallocate: when the slice is full, it returns a header pointing at a brand-new, larger backing array. The original s header still points at the old array, which may or may not have received the element. To make the result usable, you must capture it: s = append(s, 1).
Go enforces this discipline at compile time. If you write append(s, 1) as a bare statement and ignore the result, the compiler rejects it with something like append result not used. (In Go 1.22+, the diagnostic mentions the assignment explicitly.)
This is deliberate: an ignored append is almost certainly a bug, because the caller has no reliable way to know whether the element actually landed. The interview answer: a compile error — append’s result must be captured with s = append(s, x).
13. What will be the output of fmt.Println(cap(a))?
a := make([]int, 3, 7)
Output: 7.
make([]T, len, cap) takes three arguments: the slice length and its capacity. Here length is 3 and capacity is 7.
len(a)→3(visible elements).cap(a)→7(backing array slots available).
The gap between len and cap (4 slots) is headroom: appends up to the capacity won’t reallocate. The interview answer: 7 — the third argument sets the initial capacity.
Answer:
7.
make([]T, len, cap) takes three arguments: the slice length and its capacity. Here length is 3 and capacity is 7.
len(a)→3(visible elements).cap(a)→7(backing array slots available).
The gap between len and cap (4 slots) is headroom: appends up to the capacity won’t reallocate. The interview answer: 7 — the third argument sets the initial capacity.
14. What happens if you re-slice a slice beyond its length but within its capacity (s[1:5] where len(s)=3, cap(s)=8)?
Answer: It succeeds — re-slicing is allowed up to the capacity, extending the slice’s visible length into the capacity window.
The slice bounds rules: the high index can be at most cap(s) (and since Go 1.2, re-slicing may even use the capacity as the limit); the low index at most len(s). Accessing elements beyond len would panic, but re-slicing into spare capacity is legal.
s := make([]int, 3, 8) // len 3, cap 8
t := s[1:5] // OK — 5 ≤ cap 8; t has len 4
t now shows elements 1 through 4, reaching into slots that existed in the backing array but weren’t part of s’s length. The new elements are whatever was in the backing array (zeros for freshly made slices). The interview answer: legal — you may re-slice up to the capacity; only exceeding capacity panics.
Answer:
It succeeds — re-slicing is allowed up to the capacity, extending the slice’s visible length into the capacity window.
The slice bounds rules: the high index can be at most cap(s) (and since Go 1.2, re-slicing may even use the capacity as the limit); the low index at most len(s). Accessing elements beyond len would panic, but re-slicing into spare capacity is legal.
s := make([]int, 3, 8) // len 3, cap 8
t := s[1:5] // OK — 5 ≤ cap 8; t has len 4
t now shows elements 1 through 4, reaching into slots that existed in the backing array but weren’t part of s’s length. The new elements are whatever was in the backing array (zeros for freshly made slices). The interview answer: legal — you may re-slice up to the capacity; only exceeding capacity panics.
Premium Content
Unlock Slices & Maps and all premium lessons with a subscription.
From ₹199.99/year — See plans