Polyglot Primer

Chapter 6: Collections - Arrays, Lists, Maps and Sets

The workhorse data structures. Go has just arrays, slices and maps built into the language; Java ships a full Collections Framework; JS has arrays, objects, Map and Set; Python has list, tuple, dict and set with literal syntax.

Overview

NeedGoJavaJavaScriptPython
Fixed-size sequencearray [3]intarray int[]-tuple (immutable)
Growable sequenceslice []intArrayList<Integer>Arraylist
Key/valuemap[string]intHashMap<String,Integer>Map, plain Objectdict
Unique valuesmap[T]struct{} (no set type)HashSet<T>Setset
Immutable listnoList.of(...)Object.freeze([..])tuple
Queue / dequeslice or channelArrayDequeArray (shift is O(n))collections.deque
Sorted mapnone in stdlibTreeMapnonenone (sort keys)
Insertion ordered mapnone (maps are unordered)LinkedHashMapMap (ordered)dict (ordered since 3.7)
Literal syntax[]int{1,2,3}List.of(1,2,3)[1,2,3][1,2,3]
Generics neededyes (1.18+)yesnono

Arrays and tuples

var a [3]int                 // [0 0 0] - the size is part of the TYPE
b := [3]int{1, 2, 3}
c := [...]int{1, 2, 3, 4}    // compiler counts
d := b                       // arrays are VALUES: d is a full copy
int[] a = new int[3];        // [0, 0, 0], length fixed at creation
int[] b = {1, 2, 3};
int[][] grid = new int[3][4];
a.length;                    // field, not a method
// JavaScript has no fixed arrays; Array is dynamic. Typed arrays exist for binary data:
const bytes = new Uint8Array(4);
point = (3, 4)               # tuple: immutable, hashable (usable as dict key)
x, y = point
single = (5,)                # the comma makes the tuple

Go arrays are rarely used directly; slices are the norm.

Lists and slices

nums := []int{1, 2, 3}
nums = append(nums, 4, 5)           // append RETURNS a possibly new slice
nums[0] = 99
fmt.Println(len(nums), cap(nums))
sub := nums[1:3]                    // [2 3] - SHARES memory with nums!
copySlice := slices.Clone(nums)     // Go 1.21+ "slices" package
nums = slices.Delete(nums, 1, 2)    // remove index 1
make([]int, 0, 10)                  // length 0, capacity 10
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3));
nums.add(4);
nums.set(0, 99);
nums.get(1);
nums.size();
nums.remove(Integer.valueOf(2));    // remove(int) removes by INDEX - overload trap
List<Integer> sub = nums.subList(1, 3);   // a view, not a copy
List<Integer> fixed = List.of(1, 2);      // immutable: add() throws UnsupportedOperationException
const nums = [1, 2, 3];
nums.push(4);
nums[0] = 99;
nums.length;
nums.at(-1);                        // last element
nums.splice(1, 1);                  // remove index 1 (mutates)
const sub = nums.slice(1, 3);       // copy
const copy = [...nums];             // shallow copy
nums = [1, 2, 3]
nums.append(4)
nums[0] = 99
nums[-1]                            # last element
len(nums)
del nums[1]                         # or nums.pop(1)
sub = nums[1:3]                     # a copy
copy = nums.copy()                  # or nums[:] or list(nums)

Slice internals (Go)

A slice is a small struct {pointer, length, capacity} pointing into an array. append writes in place when capacity allows, otherwise allocates a bigger array and copies. Hence:

a := []int{1, 2, 3, 4}
b := a[:2]
b = append(b, 100)      // overwrites a[2]!  a is now [1 2 100 4]

Two slices can alias the same data; always use the result of append, and slices.Clone when you need independence.

Common list operations

TaskGoJavaJavaScriptPython
Add to ends = append(s, x)l.add(x)a.push(x)l.append(x)
Insert at islices.Insert(s, i, x)l.add(i, x)a.splice(i, 0, x)l.insert(i, x)
Remove by indexslices.Delete(s, i, i+1)l.remove(i)a.splice(i, 1)del l[i] / l.pop(i)
Containsslices.Contains(s, x)l.contains(x)a.includes(x)x in l
Index ofslices.Index(s, x)l.indexOf(x)a.indexOf(x)l.index(x) (raises)
Reverse in placeslices.Reverse(s)Collections.reverse(l)a.reverse()l.reverse()
Sort in placeslices.Sort(s)Collections.sort(l) / l.sort(null)a.sort((x,y)=>x-y)l.sort()
Sorted copyclone then sortl.stream().sorted().toList()a.toSorted() (ES2023)sorted(l)
Min / Maxslices.Min(s)Collections.min(l)Math.min(...a)min(l)
Sumloopstream().mapToInt(..).sum()a.reduce((s,x)=>s+x, 0)sum(l)

JS gotcha: [10, 9, 1].sort() sorts as strings by default -> [1, 10, 9]. Always pass a comparator for numbers.

Transforming: map / filter / reduce

// No built-ins: write the loop. This is idiomatic Go.
var evens []int
for _, n := range nums {
    if n%2 == 0 { evens = append(evens, n*n) }
}
List<Integer> evens = nums.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .toList();                         // Java 16+
int total = nums.stream().mapToInt(Integer::intValue).sum();
const evens = nums.filter((n) => n % 2 === 0).map((n) => n * n);
const total = nums.reduce((acc, n) => acc + n, 0);
evens = [n * n for n in nums if n % 2 == 0]       # list comprehension
total = sum(nums)
squares = {n: n * n for n in nums}                 # dict comprehension
unique = {n % 3 for n in nums}                     # set comprehension
lazy = (n * n for n in nums)                       # generator expression

Style spectrum: Go = explicit loops; Java = streams; JS = chained array methods; Python = comprehensions.

Maps / dictionaries

ages := map[string]int{"ada": 36, "linus": 54}
ages["grace"] = 85
v := ages["missing"]                // 0 - the ZERO VALUE, not an error
v, ok := ages["missing"]            // comma-ok idiom: ok == false
delete(ages, "ada")
len(ages)
for k, v := range ages { }          // order is random
var m map[string]int                // nil map: reading is fine, WRITING PANICS
m = make(map[string]int)
Map<String, Integer> ages = new HashMap<>();
ages.put("ada", 36);
ages.get("missing");                // null
ages.getOrDefault("missing", 0);
ages.containsKey("ada");
ages.remove("ada");
ages.merge("ada", 1, Integer::sum);          // counter idiom
ages.computeIfAbsent("x", k -> 0);
Map<String, Integer> fixed = Map.of("a", 1, "b", 2);   // immutable
for (Map.Entry<String, Integer> e : ages.entrySet()) { }
const ages = new Map([["ada", 36], ["linus", 54]]);
ages.set("grace", 85);
ages.get("missing");                 // undefined
ages.has("ada");
ages.delete("ada");
ages.size;
for (const [k, v] of ages) { }       // insertion order

const obj = { ada: 36 };             // plain object: also used as a dictionary
obj["grace"] = 85;
"ada" in obj;
Object.keys(obj); Object.entries(obj);
ages = {"ada": 36, "linus": 54}
ages["grace"] = 85
ages["missing"]                      # KeyError
ages.get("missing", 0)               # default
"ada" in ages
del ages["ada"]
len(ages)
for k, v in ages.items(): ...
from collections import Counter, defaultdict
Counter("banana")                    # Counter({'a': 3, 'n': 2, 'b': 1})
groups = defaultdict(list)
groups["x"].append(1)

Key requirements:

Map concurrency: Go maps and Java HashMap are not thread-safe (Go may crash with "concurrent map writes"; use sync.Mutex, sync.Map or ConcurrentHashMap).

Sets

seen := map[string]struct{}{}        // struct{} takes zero bytes
seen["a"] = struct{}{}
if _, ok := seen["a"]; ok { }
// or map[string]bool for brevity
Set<String> seen = new HashSet<>();
seen.add("a");
seen.contains("a");
Set<String> both = new HashSet<>(a); both.retainAll(b);     // intersection
const seen = new Set(["a", "b"]);
seen.add("c"); seen.has("a"); seen.delete("b");
[...new Set([1, 1, 2])];             // de-duplicate -> [1, 2]
// ES2025 added union/intersection/difference methods on Set; check your Node version.
seen = {"a", "b"}                    # note: {} alone is an empty DICT; use set()
seen.add("c")
a | b; a & b; a - b; a ^ b           # union, intersection, difference, symmetric difference

Python sets have first-class set algebra; Go has no set type at all.

Sorting with custom order

people := []Person{{"Ada", 36}, {"Linus", 54}}
slices.SortFunc(people, func(a, b Person) int {
    return cmp.Compare(a.Age, b.Age)          // import "cmp"
})
people.sort(Comparator.comparingInt(Person::age).thenComparing(Person::name));
people.sort((a, b) => a.age - b.age || a.name.localeCompare(b.name));
people.sort(key=lambda p: (p.age, p.name))     # key function, not comparator
sorted(people, key=lambda p: p.age, reverse=True)

Python and Java build sorts from "key extractors"; Go and JS use comparators returning negative/zero/positive. All four sorts are stable except Go's sort.Slice / slices.Sort (use slices.SortStableFunc if you need stability).

Copying: shallow vs deep

Shallow copyDeep copy
Goslices.Clone, maps.Clonewrite it yourself (or serialise)
Javanew ArrayList<>(list)copy constructors / serialisation
JavaScript[...a], {...o}structuredClone(x)
Pythonlist.copy(), copy.copycopy.deepcopy

Assignment never copies a collection in Java, JS or Python (both names refer to one object). In Go, assigning an array copies it, but assigning a slice or map copies only the header.

Quirks and gotchas

What is idiomatic

Checklist

Exercises

In each language:

  1. Given a list of integers, produce a list of the squares of the even ones.
  2. Given a list of words, build a map from first letter to list of words starting with it.
  3. Given two lists, compute their intersection and the elements unique to each, using a set.
  4. Sort a list of {name, age} records by age descending then name ascending.
  5. Write chunk(list, n) that splits a list into pieces of size n (the last may be shorter). In Go, make sure modifying one chunk does not alter the original.

Then revisit the chapter 3 exercise (wordFrequencies) and finish it with a proper map/dict.