Classifying data structures
Data structures can be grouped by how they store values: primitive or composite, linear or hierarchical, fixed or growing, contiguous or linked, uniform or mixed, destructive or persistent. These labels predict performance before you read any code.
Learning goals
- Place a structure on each classification axis
- Explain what contiguous storage gains and costs compared with linked storage
- Describe persistent (immutable, shareable) structures
Prerequisites
Primitive vs non-primitive
Primitive types (int, long, double, char, boolean, …) hold a single value directly. Non-primitive (reference) types such as arrays, String and collections are objects that hold references and have methods.
Collections cannot store primitives, only their wrappers (Integer, Long), so List<int> does not compile and List<Integer> boxes every value. That boxing has a real memory and time cost, covered in Chapter 4.
| Primitive | Non-primitive |
|---|---|
| int, long, double, char, boolean | int[], String, ArrayList, user classes |
| Value stored in place | Reference to an object on the heap |
| No null, no methods | May be null, has methods |
Linear vs non-linear
In a linear structure each element has at most one predecessor and one successor: arrays, linked lists, stacks and queues. In a non-linear structure an element can connect to several others: trees and graphs.
Linear structures are traversed from one end. Non-linear ones need a traversal strategy (depth-first or breadth-first).
| Linear | Non-linear |
|---|---|
| Array, list, stack, queue, deque | Tree, heap, trie, graph |
| One path through the data | Branching paths |
| Traverse with a loop | Traverse with recursion or a queue/stack |
Static vs dynamic
A static structure has a fixed size chosen at creation: a Java array. A dynamic structure can grow and shrink: ArrayList, LinkedList, HashMap.
Dynamic does not mean free. An ArrayList is built on an array that it replaces with a bigger one when full; Chapter 4 shows why that still costs O(1) per append on average.
1int[] fixed = new int[5]; // length is 5 forever2List<Integer> grows = new ArrayList<>();3for (int i = 0; i < 1000; i++) grows.add(i); // size changes as needed
Contiguous vs linked
Contiguous structures (arrays) keep elements next to each other in memory, so element k is at base + k × size: one calculation, one read, and CPU caches love it. Linked structures keep elements in separate objects joined by references, so reaching element k means following k links.
In exchange, a linked list can insert or remove at a known node in O(1) without shifting anything. Run the animation in both modes to count memory reads.
| Operation | Array | Linked list |
|---|---|---|
| Read element k | O(1) | O(k) |
| Insert at front | O(n) shift | O(1) |
| Insert after a known node | O(n) shift | O(1) |
| Memory per element | just the value | value + reference + object header |
| Cache behaviour | excellent | poor |
Reading element k: array vs linked list
Interactive · edit the inputarray memory (value, address)
The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000.
1static int getFromArray(int[] arr, int k) {2return arr[k]; // address = base + k * 4: one memory read3}45static int getFromList(Node head, int k) {6Node cur = head;7for (int i = 0; i < k; i++) {8cur = cur.next; // pointer chase: one read per hop9}10return cur.value;11}
Live state
k
5
base
1000
Execution trace (dry run table)Show 3 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | k=5 base=1000 | The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000. |
| 2 | L2 | k=5 address=1020 | Address = 1000 + 5 × 4 = 1020. One multiplication and one addition give the location, no matter how large k is. |
| 3 | L2 | return=8 | return 8 with a single memory read: O(1) random access. |
Homogeneous vs heterogeneous
A homogeneous structure holds one element type (int[], List<String>). A heterogeneous one mixes types: a record or class with different fields, or List<Object>.
Prefer homogeneous collections of a well-chosen element type, and put mixed data into a class or record so the compiler can check it.
1int[] scores = {90, 85, 77}; // homogeneous2record Student(String name, int grade) {} // heterogeneous fields in one type3List<Student> roster = List.of(new Student("Ana", 9));
Persistent vs ephemeral
An ephemeral structure is changed in place; the old version is gone. A persistent structure keeps every earlier version available, usually by sharing unchanged parts between versions instead of copying them.
A persistent linked list shows the idea: adding at the front creates one new node whose next points at the old list, so both versions coexist.
1record Cons(int head, Cons tail) {}2Cons a = new Cons(3, new Cons(2, null)); // [3, 2]3Cons b = new Cons(9, a); // [9, 3, 2]: shares all of a4// a is unchanged and still [3, 2]; b adds one node
Mutable vs immutable
A mutable structure can be modified after construction; an immutable one cannot, so it is safe to share across threads and to use as a key. List.of, Set.of and Map.of create immutable collections that throw UnsupportedOperationException on modification.
Immutability costs copying when you want a changed version, which is why persistent structures share parts.
1List<Integer> fixed = List.of(1, 2, 3);2// fixed.add(4); // throws UnsupportedOperationException3List<Integer> editable = new ArrayList<>(fixed);4editable.add(4); // fine: a mutable copy
Practice ladder
Now try it yourself
Easy
- Classify array, linked list, stack, tree and hash map as linear or non-linear
- Why can
List<int>not be written in Java?
Medium
- List two operations where an array beats a linked list and two where it loses
- Create an immutable list of 5 values in Java and show what happens when you modify it
Advanced
- Implement a persistent stack with push and pop where old versions stay valid
Key takeaways
- Contiguous storage gives O(1) indexing and great cache use; linked storage gives cheap local inserts.
- Collections hold references, so primitives are boxed.
- Persistent structures share unchanged parts so every version stays usable.