Interview Question: Explain Slices in Golang
Slices in Go are essential for handling collections of data flexibly and efficiently. This guide will help you grasp the core concepts of slices, ensuring you are well-prepared for your upcoming Golang interview.
What Are Slices?
Slices are built on top of arrays and offer a dynamic way to manage sequences of elements. They consist of three main components:
- Pointer: Points to the first element of the underlying array.
- Length: Indicates the number of elements in the slice.
- Capacity: The maximum number of elements the slice can accommodate without requiring reallocation.
package main
import "fmt"
func main() {
var a = []int{10, 20, 30, 40, 50}
b := a[:3]
fmt.Println(b) // [10 20 30]
fmt.Println(len(b)) // 3
fmt.Println(cap(b)) // 5
}
Underlying Array: [10, 20, 30, 40, 50]
Slice Header: {
Pointer: -> [10, 20, 30, 40, 50],
Length: 3,
Capacity: 5
}
Key Properties of Slices
- Dynamic Lengths: Unlike arrays, slices can grow or shrink during runtime.
- Shared Underlying Arrays: Multiple slices can reference the same array; changes in one slice affect others.
- Value Semantics: Assigning a slice copies its header but not its underlying array.
Differences Between Slices and Arrays
-
Arrays:
- Fixed size (e.g.,
[5]int
). - Value types; copying an array duplicates the entire array.
package main import "fmt" func main() { var a [5]int b := a b[2] = 7 fmt.Println(a, b) // Output: [0 0 0 0 0] [0 0 7 0 0] }
- Fixed size (e.g.,
-
Slices:
- Dynamic lengths and capacities.
- Share the same underlying array when assigned.
package main import "fmt" func main() { a := []int{1, 2, 3, 4, 5} b := a[2:] b[0] = 0 fmt.Println(a, b) // Output: [1 2 0 4 5] [0 4 5] }
Slice Header Structure
Internally, a slice is represented by a struct:
package runtime
type slice struct {
ptr unsafe.Pointer
len int
cap int
}
When passed to functions, only the slice header is copied; the underlying array remains unchanged.
Common Operations on Slices
-
Appending Elements: Use the
append
function to add elements dynamically.slice := []int{1, 2, 3} slice = append(slice, 4, 5) fmt.Println(slice) // Output: [1, 2, 3, 4, 5]
-
Copying Slices: Use
copy
to transfer elements between slices.src := []int{1, 2, 3} dst := make([]int, len(src)) copy(dst, src) fmt.Println(dst) // Output: [1, 2, 3]
-
Using Slices as Stacks: Implement stack operations using slices.
Push operation:
stack := []int{} stack = append(stack, 10) // Push
Pop operation:
val := stack[len(stack)-1] // Peek stack = stack[:len(stack)-1] // Pop
-
Modifying Slices in Functions: Changes within functions affect the original slice due to shared underlying arrays.
func negate(s []int) { for i := range s { s[i] = -s[i] } } func main() { a := []int{1, 2, 3} negate(a) fmt.Println(a) // Output: [-1 -2 -3] }
-
Inserting Elements: Shift elements to insert new values.
func Insert(slice []int, index, value int) []int { slice = append(slice, 0) // Extend length copy(slice[index+1:], slice[index:]) slice[index] = value return slice } slice := []int{1, 2, 3} slice = Insert(slice, 1, 99) fmt.Println(slice) // Output: [1, 99, 2]
Nil vs. Empty Slices
- A nil slice has no underlying array.
- An empty slice has a length of zero but can still grow.
var nilSlice []int
emptySlice := make([]int, 0)
fmt.Println(nilSlice == nil) // true
fmt.Println(emptySlice == nil) // false
Conclusion
Understanding slices is crucial for efficient coding in Go. Familiarize yourself with their properties and operations to excel in your Golang interviews. Mastering these concepts will enhance your ability to write effective and maintainable code in Go.
Citations:
- Golang Interview Questions - InterviewBit
- Golang Interview Questions and Answers - Guvi
- 50 Golang Interview Questions - Educative
- Tricky Golang Interview Questions Part 1: Slice Header - Dev.to
- Golang Interview Questions - Testbook
- Golang Interview Questions - Simplilearn