Slices and Maps
Go's dynamic arrays and hash maps
Introduction
In this lesson, you'll learn about slices and maps in Go. Coming from JavaScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In JavaScript, you're familiar with go's dynamic arrays and hash maps.
Go has its own approach to go's dynamic arrays and hash maps, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
package main
import (
"fmt"
"sort"
)
func main() {
// Slice — dynamic array
nums := []int{1, 2, 3}
nums = append(nums, 4) // [1 2 3 4]
fmt.Println(len(nums)) // 4
fmt.Println(nums[1:3]) // [2 3]
sub := nums[1:3] // slice (shares memory!)
// make with capacity
s := make([]int, 0, 10) // len=0, cap=10
s = append(s, 1, 2, 3)
// copy (independent)
dst := make([]int, len(nums))
copy(dst, nums)
// Sort
sort.Ints(nums)
sort.Slice(nums, func(i,j int) bool { return nums[i] < nums[j] })
// Map
m := map[string]int{"a": 1}
m["b"] = 2
val, ok := m["a"] // safe get; ok=false if missing
delete(m, "a")
fmt.Println(len(m))
// Iterate
for k, v := range m { fmt.Println(k, v) }
for i, v := range nums { fmt.Println(i, v) }
}Comparing to JavaScript
Here's how you might have written similar code in JavaScript:
// Array
const nums = [1, 2, 3];
nums.push(4); // [1,2,3,4]
nums.length; // 4
nums.slice(1, 3); // [2,3]
nums.includes(2); // true
nums.sort((a,b)=>a-b);
// Map
const m = new Map();
m.set("a", 1);
m.get("a"); // 1
m.has("a"); // true
m.delete("a");
m.size;
// Iterate
for (const [k,v] of m) { console.log(k,v); }
for (const x of nums) { console.log(x); }You may be used to different syntax or behavior.
append() returns a new slice — always reassign: s = append(s, x)
You may be used to different syntax or behavior.
Slices share underlying array — use copy() for independent copies
You may be used to different syntax or behavior.
make([]T, len, cap) pre-allocates capacity for performance
You may be used to different syntax or behavior.
Map lookup returns (value, ok) — always check ok to distinguish missing from zero value
You may be used to different syntax or behavior.
delete(m, key) removes; len(m) for size; range m iterates
Step-by-Step Breakdown
1. Slices vs Arrays
Go arrays have fixed size ([3]int). Slices are dynamic views over arrays. Use slices in practice; arrays are rarely used directly.
const nums = [1, 2, 3]; nums.push(4);nums := []int{1, 2, 3} // slice literal
nums = append(nums, 4) // always reassign!2. Slice Sharing
Slices share the underlying array. Modifying a sub-slice modifies the original. Use copy() when you need independence.
const copy = [...arr]; // spread makes new arraysub := nums[1:3] // shares memory
dst := make([]int, len(nums))
copy(dst, nums) // truly independent3. Map Safe Access
Map access in Go returns the zero value for missing keys — no undefined, no panic. Use the two-value form to distinguish zero from missing.
const v = m.get("key") ?? "default";if val, ok := m["key"]; ok {
// key exists
} else {
// key missing; val is zero value
}4. range Keyword
range iterates slices (index, value) and maps (key, value). Use _ to discard unwanted variables.
for (const [i, v] of nums.entries()) { ... }
for (const [k, v] of map) { ... }for i, v := range nums { fmt.Println(i, v) }
for k, v := range m { fmt.Println(k, v) }
for _, v := range nums { fmt.Println(v) } // discard indexCommon Mistakes
When coming from JavaScript, developers often make these mistakes:
- append() returns a new slice — always reassign: s = append(s, x)
- Slices share underlying array — use copy() for independent copies
- make([]T, len, cap) pre-allocates capacity for performance
Key Takeaways
- append returns new slice — always reassign: s = append(s, item)
- Slices share memory; copy(dst, src) for independent copies
- Map lookup: val, ok := m[key] — check ok to test key existence
- range for loops give (index, value) for slices, (key, value) for maps