Chapter 4: Collection types
Introduces Go’s built-in collection types and the kinds of data each is suited to holding.
After this chapter you will be able to
- Arrays and slices as lists. Distinguish arrays as fixed-size ordered lists from slices as dynamic ordered collections that can grow to fit data at runtime.
- Maps and built-in collections. Describe maps as collections that store values by keys, such as names or sizes, and state that Go’s collection types are built into the language rather than requiring a special import.
- Array layout and indexing. Describe a Go array as a fixed-length collection of same-type elements stored contiguously, with each element accessible by an index starting at zero.
- Array type and fixed length. Explain that an array’s length and element type together determine its type, and that neither can change after declaration. Describe creating a new array and copying values if more elements are needed.
- Array declarations and initial values. Describe declaring an array of a specified length and element type with var, or declaring and initializing one with a short variable declaration and an array literal. Explain that unspecified elements receive their element type’s zero value, and that a literal can set values at selected indices.
- Array length as type. Explain that an array’s length is part of its type, so arrays of different lengths cannot be assigned to each other and an array’s size cannot change.
- Indexed array element access. Explain how to retrieve or change an array element with square brackets and an index that is an integer literal or a variable of any integer type.
- Range loop return values. Explain what a range loop over an array provides when written with one or two variables, and how the blank identifier lets you ignore the index.
- Range values as copies. Explain why changing the value variable in a two-value range loop does not change the array, and describe how to modify the element instead.
- Array assignment and value copies. Explain that an array variable denotes the whole array and can be assigned to another array of the same type. Describe how the assignment creates a separate array containing copies of every element.
- Copied pointers and shared objects. Distinguish copying an array of pointers from copying the objects those pointers refer to: the new array has its own copied pointer values, but those pointers still point to the same memory as the originals.
- Nested array representation. Explain that Go simulates multidimensional arrays by making arrays whose elements are themselves arrays, rather than providing a separate matrix type. Describe a declaration such as [4][2]int as a length-four array of length-two arrays of integers.
- Nested array indexing. Describe how an index selects an outer array element and an additional index selects a value inside that subarray. Explain that accessing fewer levels yields a subarray, while accessing every level yields an individual value.
- Array passing by value. Explain that Go passes values by value, so passing an array to a function copies the entire array, regardless of its size. Describe why this can be costly for a large array such as the 4K image example.
- Pointer passing tradeoff. Describe how passing a pointer to an array avoids copying the entire array and instead passes a small pointer value. Explain that changes made through the pointer are reflected in the original array.
- Fixed array size limitations. Explain why arrays are useful when the number of items is fixed but often unsuitable when it is not: they cannot grow or shrink, so unused capacity wastes memory and needing more space requires rebuilding the program with a larger array.
- Array copying and slice motivation. Explain that passing an array from function to function copies it, requiring care when sharing its contents, and that slices address this problem as well as the fixed-size problem.
- Slice header and backing array. Describe the two parts of a slice: a backing array that stores elements and a slice header stored in the slice variable. State that the header holds a pointer to where the slice begins, its length, and its capacity.
- Slice flexibility versus arrays. Explain that slices are ordered collections used much like arrays but do not specify a size in their type. Describe how separating their header from array storage lets them grow or shrink and be copied without copying every stored element.
- Slice literal creation and size. Explain that a slice literal looks like an array literal without a size, and that it creates a new backing array containing the supplied values. Describe how the slice points to the array’s first element and how len and cap both equal the number of values supplied.
- Slice creation with make. Describe how make([]T, length) creates a slice of the specified element type and length, with every element set to that type’s zero value. Explain that, without a separate capacity argument, its backing array has exactly that length.
- Length and reserved capacity. Explain how make([]T, length, capacity) allocates room beyond the slice’s current length for later expansion with append. Distinguish elements accessible within the length from allocated storage reserved by the capacity, and state that capacity must be at least length.
- Nil slice state and behavior. Explain that a slice declared with var is nil because it has no backing array; its length and capacity are both zero, it compares equal to nil, and accessing an element causes a runtime panic.
- Nil slice uses and initialization. Describe when a nil slice is useful as a placeholder and how to assign it a value before accessing elements, using a literal, make, append, or a slice expression.
- Append inputs and returned slice. Explain that append takes a slice and one or more values and returns a slice containing the added values, so the caller assigns the result when updating a slice. Describe how to append multiple values or unpack another slice with ...; append also works on a nil slice.
- Backing array allocation paths. Trace what append does when capacity is exhausted versus when spare capacity remains. Explain that exhaustion leads to a larger backing array, copied old values, added new values, and a returned slice header, while spare capacity lets the slice grow without reallocating.
- Append and backing array changes. Explain why a function can modify a caller’s slice elements normally, yet an append may cause its later changes to be invisible to the caller: append can allocate a new backing array, leaving the two slices pointing to different storage.
- Extra capacity and slice length. Explain why giving the caller’s slice extra capacity avoids allocation but still does not make an element appended inside the function accessible through the caller’s slice: the caller’s slice length remains unchanged.
- Safe append across functions. State the section’s two ways to avoid these surprises: append before passing the slice to a function, or have the function return the appended slice and use that returned slice.
- Slice expression ranges and defaults. Explain how a[low:high] selects a range from a slice or array, with omitted low defaulting to 0 and omitted high defaulting to len(a). Describe the result as a new slice that shares the source's storage rather than copying its values.
- Three-index capacity limit. Explain that an ordinary slice expression can retain the remaining source storage as capacity, while a[low:high:max] cuts the new slice's capacity off at max to prevent appending into the original storage beyond that point.
- Copy length and destination behavior. Explain that copy(dst, src) copies elements between slices of the same type, returns the number copied, and copies no more than the length of the shorter slice. Describe what happens when either slice has length zero or the destination is longer than the source.
- Independent slice copy. Describe how to make a separate copy of a slice expression or preserve a slice around an append that might modify it: create a destination of the needed length with make, then copy the elements into it.
- Out-of-bounds slice panics. Explain why a function that indexes the first three elements of a slice works with three or more entries but panics with fewer: slice length is determined at runtime, and an explicitly requested missing element cannot be accessed.
- Length checks before indexing. Describe how checking len before accessing the first three elements lets the function return an error for a shorter slice instead of panicking, and why callers then need to check that error.
- Flexible range loop traversal. Explain how a range loop avoids direct index access and handles slices with any number of entries, including an empty slice, which makes the function more flexible than requiring three finishers.
- Nil and empty slice checks. Distinguish a nil slice from a zero-length slice, and explain why `len(slice) == 0` is the reliable emptiness check while `slice == nil` answers a different question.
- Keyed map values. Distinguish maps from arrays and slices: maps associate values with keys rather than integer positions. Describe how a meaningful key, such as a fruit name, can identify a value, such as its weight.
- Hash based bucket selection. Explain that a map hashes a key to select a bucket when storing, removing, or looking up a pair. Describe why distributing pairs evenly across buckets keeps retrieval fast as the map grows.
- Map declaration and initialization. Describe the map[K]E type, where K is the key type and E is the stored value type, and explain how make and map literals create maps. State that a literal can also supply initial key/value pairs.
- Nil map initialization safeguards. Explain that a map declared with var but not initialized is nil, with no internal storage allocated, and that writing to it panics. Describe checking for nil and initializing it before use, and why the section generally favors map literals.
- Missing keys and existence checks. Explain what a map lookup returns for a missing key and how the two-variable assignment form distinguishes a missing key from a stored zero value.
- Zero-value lookup patterns. Explain when the zero value makes an existence check unnecessary, using the section’s Boolean membership map and numeric word-count map as examples.
- Map range loop values. Describe how a range loop visits a map, yielding a key and, optionally, a copy of its value on each iteration.
- Unspecified map iteration order. Explain that map keys have no specified iteration order, so a range loop may produce them in different orders across runs or architectures.
- Filtering maps during iteration. Describe how to combine a map range loop with delete to remove entries that fail a criterion, such as people who are not over 18. State that this is safe provided the map is not being accessed elsewhere at the same time.
- Map changes across function calls. Explain that a map points to an underlying runtime-allocated data structure, so changes to its contents inside a function are visible to the caller.
- Map key type restrictions. Explain that map values can have any type, while keys must support == comparison. Describe why slices, maps, functions, and structs containing them cannot be keys, although a comparable struct can be a key with a slice as its value.
What it builds on
- Basic for loops (chapter 2) · Introduces basic for loops.
- Dereferencing pointers (chapter 3) · Dereferencing pointers with the indirection operator.
- Pointer types (chapter 3) · Pointers hold references to memory.
- Slices and len (chapter 2) · Introduces slices and len.
- Basic error handling and the log package (chapter 2) · Introduces basic error handling.
- The bool type (chapter 3) · The bool type and its false zero value
- Structs as types (chapter 3) · Structs as types
Section by section
Introduction
Introduces Go’s built-in collection types and the kinds of data each is suited to holding.
- Arrays and slices as lists: Distinguish arrays as fixed-size ordered lists from slices as dynamic ordered collections that can grow to fit data at runtime.
Why it matters: Choosing between arrays and slices starts with whether the collection needs to grow. - Maps and built-in collections: Describe maps as collections that store values by keys, such as names or sizes, and state that Go’s collection types are built into the language rather than requiring a special import.
Why it matters: Maps provide a way to organize data by a chosen key instead of by its place in a list.
Key terms: arrays, slices, fixed-size, dynamic, maps, key-value data, built into the language
4.1 Arrays
Introduces arrays as fixed-length, contiguous collections and explains how indexing and memory layout affect access and iteration.
- Array layout and indexing: Describe a Go array as a fixed-length collection of same-type elements stored contiguously, with each element accessible by an index starting at zero.
Why it matters: This is the foundation for understanding array declarations and element access. - Contiguous storage and iteration: Explain why contiguous array storage can help CPU caching and why Go uses indexes rather than pointer arithmetic to access array or slice elements.
Why it matters: It connects the array’s layout to efficient iteration and Go’s access rules.
Key terms: array, fixed-length, contiguous, index, CPU caches, index arithmetic, pointer arithmetic
4.1.1 Declaring arrays
This section explains how to declare Go arrays, initialize their elements, and choose between explicit and compiler-inferred lengths.
- Array type and fixed length: Explain that an array’s length and element type together determine its type, and that neither can change after declaration. Describe creating a new array and copying values if more elements are needed.
Why it matters: An array’s fixed length affects which arrays are the same type and what you must do when one is too small. - Array declarations and initial values: Describe declaring an array of a specified length and element type with var, or declaring and initializing one with a short variable declaration and an array literal. Explain that unspecified elements receive their element type’s zero value, and that a literal can set values at selected indices.
Why it matters: These declaration forms let you choose which array elements start with explicit values and which start with defaults. - Inferred array length caution: Explain that using … in place of a literal’s length makes the compiler determine the array length. State why the section recommends care with this convenience, particularly in test fixtures.
Why it matters: An inferred length is convenient, but an unintended length changes the resulting array type.
Key terms: array length, element type, array type, var, short variable declaration, array literal, zero value, indices, automatic array length, …
4.1.2 Array type and length
This section explains that an array’s length is part of its type and that len reports its fixed size.
- Array length as type: Explain that an array’s length is part of its type, so arrays of different lengths cannot be assigned to each other and an array’s size cannot change.
Why it matters: This explains why arrays of different sizes are not interchangeable in Go. - Array length from len: Describe how len() returns an int giving the array’s full number of element positions, whether or not values have been explicitly initialized.
Why it matters: It distinguishes an array’s capacity to hold elements from the number of values explicitly initialized.
Key terms: array length, array type, [5]int, [4]int, len(), int, array size
4.1.3 Working with array elements
This section shows how to read and change array elements by index, including elements that hold pointers.
- Indexed array element access: Explain how to retrieve or change an array element with square brackets and an index that is an integer literal or a variable of any integer type.
Why it matters: Indexing lets you work with individual values in an array. - Pointer valued array elements: Describe how to access the value pointed to by an array element that holds a pointer, using the indirection operator.
Why it matters: An array element that holds a pointer is distinct from the value it points to.
Key terms: array elements, square bracket notation, index, pointer, indirection operator, array of pointers
4.1.4 Iterating over arrays with for
This section shows how to traverse arrays with range or index-based for loops, including how to modify elements and control traversal order.
- Range loop return values: Explain what a range loop over an array provides when written with one or two variables, and how the blank identifier lets you ignore the index.
Why it matters: These forms let you choose whether a loop needs indices, values, or both. - Range values as copies: Explain why changing the value variable in a two-value range loop does not change the array, and describe how to modify the element instead.
Why it matters: Recognizing the copy prevents a loop from appearing to update an array when it does not. - Index controlled traversal: Describe using a traditional for loop with len and indices to traverse an array, and explain when it is preferable to range.
Why it matters: Index-controlled loops make it possible to visit elements in a chosen order.
Key terms: range clause, index, blank identifier (_), range, copy, pointers, for loop, len, indices
4.1.5 Arrays as values
This section explains that assigning an array copies the entire array, including its elements, while copied pointers can still refer to the same underlying objects.
- Array assignment and value copies: Explain that an array variable denotes the whole array and can be assigned to another array of the same type. Describe how the assignment creates a separate array containing copies of every element.
Why it matters: This tells you whether changing an element in one array will change the other. - Copied pointers and shared objects: Distinguish copying an array of pointers from copying the objects those pointers refer to: the new array has its own copied pointer values, but those pointers still point to the same memory as the originals.
Why it matters: Separate arrays do not necessarily mean separate underlying objects.
Key terms: array value, same type, array assignment, pointer types, pointer values, same memory
4.1.6 Multidimensional arrays
This section shows how Go represents multidimensional data with nested arrays and how indexing, copying, and type inspection work with them.
- Nested array representation: Explain that Go simulates multidimensional arrays by making arrays whose elements are themselves arrays, rather than providing a separate matrix type. Describe a declaration such as [4][2]int as a length-four array of length-two arrays of integers.
Why it matters: Reading each level of the type makes nested arrays easier to declare and use. - Nested array indexing: Describe how an index selects an outer array element and an additional index selects a value inside that subarray. Explain that accessing fewer levels yields a subarray, while accessing every level yields an individual value.
Why it matters: The number of index operations determines whether you are working with a whole subarray or one of its values. - Nested array types and copies: Explain that nested arrays can be copied into one another when their element types and lengths match at every level, because arrays are values. Describe using fmt.Printf with %T to reveal a collection variable’s complete type.
Why it matters: The complete nested type determines which arrays can be copied into each other.
Key terms: multidimensional arrays, arrays of arrays, element type, inner values, subarrays, values, same element type, lengths, %T, fmt.Printf
4.1.7 Passing arrays to functions
This section explains why passing large arrays to functions can be costly and how passing a pointer avoids copying the array while allowing changes to affect the original.
- Array passing by value: Explain that Go passes values by value, so passing an array to a function copies the entire array, regardless of its size. Describe why this can be costly for a large array such as the 4K image example.
Why it matters: It explains the performance and memory cost behind the section’s recommendation to use a pointer for large arrays. - Pointer passing tradeoff: Describe how passing a pointer to an array avoids copying the entire array and instead passes a small pointer value. Explain that changes made through the pointer are reflected in the original array.
Why it matters: A pointer can make processing a large array more efficient, but it changes whether the function’s edits affect the caller’s data.
Key terms: passed by value, array, copy, pointer to an array, processPixels, original value
4.1.8 The problem with arrays
This section explains why fixed-size, copied-by-value arrays are often awkward for lists and introduces slices as the solution.
- Fixed array size limitations: Explain why arrays are useful when the number of items is fixed but often unsuitable when it is not: they cannot grow or shrink, so unused capacity wastes memory and needing more space requires rebuilding the program with a larger array.
Why it matters: This is a central reason the chapter turns from arrays to slices. - Array copying and slice motivation: Explain that passing an array from function to function copies it, requiring care when sharing its contents, and that slices address this problem as well as the fixed-size problem.
Why it matters: The copying issue is the other reason this section gives for preferring slices for lists.
Key terms: arrays, fixed number of items, slices, copied
4.2 Slices
The section introduces slices as flexible, ordered collections and explains how a slice header provides a dynamic view of an underlying array.
- Slice header and backing array: Describe the two parts of a slice: a backing array that stores elements and a slice header stored in the slice variable. State that the header holds a pointer to where the slice begins, its length, and its capacity.
Why it matters: The header explains how a slice can describe a changing view of array storage. - Slice flexibility versus arrays: Explain that slices are ordered collections used much like arrays but do not specify a size in their type. Describe how separating their header from array storage lets them grow or shrink and be copied without copying every stored element.
Why it matters: This distinction explains why slices are Go’s primary ordered collection type.
Key terms: backing array, slice header, length, capacity, slices, arrays, dynamic view
4.2.1 Declaring slices with slice literals
This section shows how a slice literal creates a slice and determines its backing array, length, and capacity.
- Slice literal creation and size: Explain that a slice literal looks like an array literal without a size, and that it creates a new backing array containing the supplied values. Describe how the slice points to the array’s first element and how len and cap both equal the number of values supplied.
Why it matters: This connects the values written in a slice literal to the slice’s storage and its initial length and capacity.
Key terms: slice literal, backing array, len, cap, length, capacity
4.2.2 Declaring slices with make
This section shows how to create zero-initialized slices with make and how length and capacity determine which allocated elements are accessible.
- Slice creation with make: Describe how make([]T, length) creates a slice of the specified element type and length, with every element set to that type’s zero value. Explain that, without a separate capacity argument, its backing array has exactly that length.
Why it matters: This is the basic form for creating a slice whose elements are ready to use. - Length and reserved capacity: Explain how make([]T, length, capacity) allocates room beyond the slice’s current length for later expansion with append. Distinguish elements accessible within the length from allocated storage reserved by the capacity, and state that capacity must be at least length.
Why it matters: Separating length from capacity explains both what you can access now and why a slice can grow without a new backing array. - Empty slice with allocated storage: Describe why a slice made with length zero and positive capacity appears empty even though an array of the specified capacity backs it.
Why it matters: An empty-looking slice can already have storage reserved for growth.
Key terms: make, length, zero value, backing array, capacity, append, runtime panic, length 0
4.2.3 Nil slices
This subsection explains what a nil slice is, what operations work on it, and how to give it values before accessing elements.
- Nil slice state and behavior: Explain that a slice declared with var is nil because it has no backing array; its length and capacity are both zero, it compares equal to nil, and accessing an element causes a runtime panic.
Why it matters: A nil slice can look like an empty collection, but trying to access an element will panic. - Nil slice uses and initialization: Describe when a nil slice is useful as a placeholder and how to assign it a value before accessing elements, using a literal, make, append, or a slice expression.
Why it matters: You can leave an optional slice nil or fill it later, provided you assign it a value before accessing elements.
Key terms: nil slice, backing array, len, cap, runtime panic, nil slices, placeholders, literals, make, append
4.2.4 Growing slices with append
This section shows how to grow slices with append, including when it reuses the backing array and when it allocates a new one.
- Append inputs and returned slice: Explain that append takes a slice and one or more values and returns a slice containing the added values, so the caller assigns the result when updating a slice. Describe how to append multiple values or unpack another slice with ...; append also works on a nil slice.
Why it matters: Using the returned slice matters because append may give you a new slice header and backing array. - Backing array allocation paths: Trace what append does when capacity is exhausted versus when spare capacity remains. Explain that exhaustion leads to a larger backing array, copied old values, added new values, and a returned slice header, while spare capacity lets the slice grow without reallocating.
Why it matters: The allocation path determines whether an append reuses existing storage or moves the slice's values elsewhere. - Extra capacity and copying cost: Justify why append allocates more capacity than immediately needed: spare room avoids copying the values on every later append. Describe the section's growth figures as a current implementation detail rather than a permanent guarantee.
Why it matters: Extra capacity explains why repeated appends can be cheaper than repeatedly copying an entire slice.
Key terms: append, nil slice, ... operator, length, capacity, backing array, slice header, array doubling, amortized doubling
4.2.5 Avoiding append surprises
This section explains why appending inside a function can make changes invisible to the caller and how to avoid that surprise.
- Append and backing array changes: Explain why a function can modify a caller’s slice elements normally, yet an append may cause its later changes to be invisible to the caller: append can allocate a new backing array, leaving the two slices pointing to different storage.
Why it matters: It explains why changes made inside a function can appear to vanish after an append. - Extra capacity and slice length: Explain why giving the caller’s slice extra capacity avoids allocation but still does not make an element appended inside the function accessible through the caller’s slice: the caller’s slice length remains unchanged.
Why it matters: Shared storage alone is not enough for the caller to access an appended element. - Safe append across functions: State the section’s two ways to avoid these surprises: append before passing the slice to a function, or have the function return the appended slice and use that returned slice.
Why it matters: These approaches keep the caller using a slice that includes the appended values.
Key terms: append, backing array, slice, capacity, length, return the appended slice
4.2.6 Slice expressions
This section shows how slice expressions create shared-storage windows into slices or arrays, how a third index limits capacity, and how to pass a subset to a function.
- Slice expression ranges and defaults: Explain how a[low:high] selects a range from a slice or array, with omitted low defaulting to 0 and omitted high defaulting to len(a). Describe the result as a new slice that shares the source's storage rather than copying its values.
Why it matters: The range and shared storage determine both which values the new slice contains and how it relates to the original data. - Three-index capacity limit: Explain that an ordinary slice expression can retain the remaining source storage as capacity, while a[low:high:max] cuts the new slice's capacity off at max to prevent appending into the original storage beyond that point.
Why it matters: A capacity limit helps prevent an append to a subslice from overwriting later values in its source. - Subset passed to functions: Describe using a slice expression to pass just a subset of a larger slice to a function, such as passing selected float64 readings to an averaging function written with range and len.
Why it matters: The same function can operate on a chosen portion of the data without needing a separate averaging implementation.
Key terms: slice expression, low, high, backing array, slice expressions with capacity, a[low : high : max], capacity, max, range, len
4.2.7 Copying slices with copy
This section explains how to use Go’s copy function to duplicate slice elements without relying on append or shared slice expressions.
- Copy length and destination behavior: Explain that copy(dst, src) copies elements between slices of the same type, returns the number copied, and copies no more than the length of the shorter slice. Describe what happens when either slice has length zero or the destination is longer than the source.
Why it matters: The destination’s length determines how many elements copy can receive; copy does not grow it for you. - Independent slice copy: Describe how to make a separate copy of a slice expression or preserve a slice around an append that might modify it: create a destination of the needed length with make, then copy the elements into it.
Why it matters: A separately allocated destination preserves the original values when later slice operations might change them.
Key terms: copy, src, dst, length, append, slice expression, make, destination slice
4.2.8 Avoiding runtime panics when accessing slice values
This section explains why indexing a slice can panic at runtime and compares length checks, range loops, and deliberate panics as ways to handle slices of unexpected size.
- Out-of-bounds slice panics: Explain why a function that indexes the first three elements of a slice works with three or more entries but panics with fewer: slice length is determined at runtime, and an explicitly requested missing element cannot be accessed.
Why it matters: Recognizing this failure makes it easier to prevent a common Go runtime panic. - Length checks before indexing: Describe how checking len before accessing the first three elements lets the function return an error for a shorter slice instead of panicking, and why callers then need to check that error.
Why it matters: A length check makes a minimum-size requirement explicit and gives callers a recoverable error. - Flexible range loop traversal: Explain how a range loop avoids direct index access and handles slices with any number of entries, including an empty slice, which makes the function more flexible than requiring three finishers.
Why it matters: Range lets the work adapt to the entries actually present instead of assuming a minimum length. - Choosing intentional panic behavior: Justify when leaving an out-of-bounds panic may be appropriate: an unexpected slice length can signal a serious bug, whereas a legitimately variable length calls for a length check or range loop.
Why it matters: The right response depends on whether a short slice is a valid input or evidence of a broken assumption.
Key terms: slice, index, runtime panic, out-of-bounds, len, length check, error, range loop, index access, panic
4.2.9 Nil slices versus empty slices
This section explains why a zero-length slice is not necessarily nil and how to choose the right emptiness check.
- Nil and empty slice checks: Distinguish a nil slice from a zero-length slice, and explain why `len(slice) == 0` is the reliable emptiness check while `slice == nil` answers a different question.
Why it matters: A nil check can miss an empty slice and produce surprising behavior.
Key terms: nil slice, empty slice, len, backing array, slice header
4.3 Maps
This section introduces maps as unordered key/value collections and explains how hashing makes key-based lookup efficient.
- Keyed map values: Distinguish maps from arrays and slices: maps associate values with keys rather than integer positions. Describe how a meaningful key, such as a fruit name, can identify a value, such as its weight.
Why it matters: Thinking in key/value pairs helps you choose a map when positional indexing would be awkward. - Hash based bucket selection: Explain that a map hashes a key to select a bucket when storing, removing, or looking up a pair. Describe why distributing pairs evenly across buckets keeps retrieval fast as the map grows.
Why it matters: The bucket model explains why maps are useful for finding a value without scanning every pair. - Go map bucket layout: Describe the implementation details given here: low-order bits of a key's hash select a bucket, while an array of high-order bits helps distinguish pairs within it. State that a separate byte array packs the bucket's keys together and then its values to reduce memory use.
Why it matters: These details illustrate how Go organizes entries inside buckets, though they are not required to start using maps.
Key terms: map, key, value, unordered collection, key/value pairs, hash table, bucket, hash function, key/value pair, low-order bits (LOBs)
4.3.1 Declaring maps
This section explains how to declare and initialize Go maps, and why nil maps need care before use.
- Map declaration and initialization: Describe the map[K]E type, where K is the key type and E is the stored value type, and explain how make and map literals create maps. State that a literal can also supply initial key/value pairs.
Why it matters: Choosing an initialization method lets you create a map that is ready to hold values. - Nil map initialization safeguards: Explain that a map declared with var but not initialized is nil, with no internal storage allocated, and that writing to it panics. Describe checking for nil and initializing it before use, and why the section generally favors map literals.
Why it matters: A nil check and initialization can prevent a runtime panic when code needs to add map entries.
Key terms: map[K]E, key type, element type, make, map literal, var, nil map, == nil, runtime panic
4.3.2 Setting and accessing map values
This section explains how to set and retrieve map values, check whether a key exists, and use zero values for convenient lookups.
- Map assignments and overwrites: Describe how to assign a map value using a key and an element of the correct types, and what happens when the key already exists.
Why it matters: Assigning to an existing key updates its value rather than adding a second entry. - Missing keys and existence checks: Explain what a map lookup returns for a missing key and how the two-variable assignment form distinguishes a missing key from a stored zero value.
Why it matters: A zero-valued result alone cannot tell you whether the key was present. - Zero-value lookup patterns: Explain when the zero value makes an existence check unnecessary, using the section’s Boolean membership map and numeric word-count map as examples.
Why it matters: These patterns keep lookups simple when a missing entry should behave like false or zero.
Key terms: key, element type, map value, zero value, two-variable assignment, Boolean value, membership list, set
4.3.3 Removing values from maps
4.3.4 Getting the number of values in a map
4.3.5 Iterating over maps
This section shows how to range over a Go map, explains why iteration order varies, and uses iteration with delete to filter entries.
- Map range loop values: Describe how a range loop visits a map, yielding a key and, optionally, a copy of its value on each iteration.
Why it matters: This is the basic way to examine every entry in a map. - Unspecified map iteration order: Explain that map keys have no specified iteration order, so a range loop may produce them in different orders across runs or architectures.
Why it matters: Code should not rely on a map range loop visiting keys in a particular order. - Filtering maps during iteration: Describe how to combine a map range loop with delete to remove entries that fail a criterion, such as people who are not over 18. State that this is safe provided the map is not being accessed elsewhere at the same time.
Why it matters: It lets you filter a map in place while recognizing the section's concurrent-access caveat.
Key terms: range loop, map key, copy of the value, unspecified order, range statement, delete, filtering, concurrent map access
4.3.6 Passing maps to functions
Passing a map to a function lets the function modify map contents that the caller can see.
- Map changes across function calls: Explain that a map points to an underlying runtime-allocated data structure, so changes to its contents inside a function are visible to the caller.
Why it matters: It helps you predict whether a function can change a map the caller uses.
Key terms: map, reference types, underlying data structure
4.3.7 Key and value types
Map values can have any type, but map keys must be comparable with ==, which allows some structs as keys but rules out slices, maps, functions, and structs containing them.
- Map key type restrictions: Explain that map values can have any type, while keys must support == comparison. Describe why slices, maps, functions, and structs containing them cannot be keys, although a comparable struct can be a key with a slice as its value.
Why it matters: The key’s type determines whether a map declaration is valid, independently of the value’s type.
Key terms: key types, map elements, comparable, == operator, struct type, slice value
4.4 Summary
The summary contrasts Go’s three collection types and highlights how slice growth and shared data affect their use.
Where this is used later
- Pointer types (chapter 5) · arrays as values
- Complex types (chapter 5) · Using slices to hold sequences of values
- Multiple type parameters (chapter 6) · Introduces built-in maps.
- Summary (chapter 6) · maps and their key and value types
- Mutexes (chapter 9) · The concurrent-access caveat motivates protecting shared maps when using mutexes.
Concepts explained in this chapter
- arrayAn array is a fixed-length, indexed sequence of values of one element type that is itself a value in Go.
- MapA map is a Go associative data structure that associates keys with values.
- slicingA slice expression is Go syntax that creates a slice view over an index range of an array or slice while sharing the underlying storage.
- backing arrayA backing array is the underlying contiguous storage that holds the elements accessible through a slice and any reserved capacity.
- capacitySlice capacity is the maximum number of elements a Go slice can contain by extending from its first element within its underlying storage.
- len()len is a Go built-in function that returns the number of bytes in a string or the number of elements in a supported collection.
- appendappend is a Go built-in function that adds elements to a slice and returns a slice representing the resulting sequence.
- range loopA range loop is a Go for-loop form that iterates over the elements or entries of a supported collection and can provide each element's index and value.
- makemake is a Go built-in function that allocates and initializes slices, maps, or channels with a specified type and size parameters.
- panicA panic is Go's runtime mechanism for terminating ordinary execution and reporting an unrecoverable run-time error unless recovered.
- zero valueA zero value is the default value assigned to a newly declared Go variable when no explicit initial value is provided.
- loopA for loop is a Go control-flow construct that repeatedly executes a block according to optional initialization and post clauses and a loop condition or range clause.
- value copyCopy by value is an assignment behavior in which a variable receives an independent copy of another value's data.
- pointerA pointer type is a Go type whose values identify the memory locations of values of a specified target type.
- data typeA type is a specification of the values and operations permitted for a piece of data in a programming language.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
