Compare static and dynamic data structures, write programs with 1D and 2D arrays, ArrayLists and Python lists, and explain how stacks and queues work, what they cost and when to choose each.
Each is in the Computer Science glossary, with every other term in the subject.
A fixed-size, ordered collection of elements of the same data type, stored contiguously in memory and accessed by an integer index starting at 0.
A Java class that stores an ordered, indexed collection of objects in an internal array, which it replaces with a larger one automatically when it fills.
The number of elements a dynamic array can hold in its current block of memory before it must allocate a larger block.
A queue stored in a fixed-size array in which the front and rear pointers wrap round from the last index to index 0, so slots freed by dequeuing are reused.
A way of organising and storing a collection of data items in memory, together with the operations used to access and change them.
A double-ended queue, a data structure that allows items to be added and removed efficiently at both its front and its rear.
The queue operation that removes the item at the front of a queue and returns it.
A data structure whose size can change while the program runs, with memory allocated and released as items are added and removed.
Compare static and dynamic data structures in terms of memory allocation, resizing, speed, memory use and flexibility.
Construct programs that create, update and traverse one-dimensional arrays and lists, with and without built-in functions.
Construct programs that process two-dimensional arrays and lists with nested loops.
Construct programs that add, insert and remove elements in a Java ArrayList or a Python list.
Explain the concept of a stack as a last in, first out (LIFO) structure, using push, pop, peek and isEmpty.
Explain the concept of a queue as a first in, first out (FIFO) structure, using enqueue, dequeue, front and isEmpty, including the circular queue.
Trace a sequence of stack and queue operations, recording the pointers and the values returned.
Explain how stack and queue operations affect performance and memory, and justify the choice of a stack or a queue for a given problem.
A program rarely works with one value at a time. A weather station logs a reading every minute, a game board holds 64 squares, a text editor remembers every change so that it can undo them, and a printer holds a line of documents waiting their turn. A data structure groups many values under one name and fixes how they are stored and reached, so the same few lines of code can process ten values or ten million. The choice of structure decides how fast the program runs, how much memory it uses and how easily it copes when the amount of data changes.
Two families of structure appear in almost every program: arrays and lists, which give direct access to any element by position, and stacks and queues, which restrict access to one end so that items come out in a fixed order. Both rely on the same idea of how memory is reserved, and both are built from the variables, data types and loops of subtopic B2.1, Programming fundamentals.
211 exam-style questions on this subtopic, written in the style of the IB papers. The first has its markscheme open. Counted on
A college print room sends every print job to a single printer. Jobs wait in a queue and the printer always takes the job at the front. The queue starts empty, and then these operations happen in order:
enqueue(J1), enqueue(J2), enqueue(J3), dequeue(), enqueue(J4), dequeue(), enqueue(J5)
State the job that the printer will print next.
1 mark
J3;
State the contents of the queue after the operations, from front to rear.
2 marks
Award [1] for J3 at the front and [1] for J4, J5 following in that order.
J3 at the front;
then J4, then J5 at the rear;
Note: Accept a labelled diagram of the queue.
Outline one reason why a queue is a suitable structure for the print jobs.
2 marks
One of 60 short multiple-choice questions on B2.2. Pick an answer to see why it's right or wrong.
A dynamic array holds 8 elements at indices 0 to 7. How many existing elements must move when a new element is inserted at index 2?
The official deck for this part of the syllabus.
Define data structure.
Why does a program use a data structure rather than one variable per value?
With a free account, Augment remembers where you stopped and what you have practiced.
A single value stored in an array or list, identified by its index.
The queue operation that adds a new item at the rear of a queue.
The rule that the item added earliest is the first to be removed.
The end of a queue from which items are removed, and the operation that returns the item there without removing it; in an array-based queue, a pointer holds its index.
An operation on a stack or queue that returns true if it holds no items and false otherwise.
The rule that the item added most recently is the first to be removed.
Python's built-in ordered, mutable and dynamic sequence, indexed from 0, which grows and shrinks as elements are added and removed.
The reservation of a region of memory to hold a program's data, either once when a structure is created or repeatedly while the program runs.
The stack operation that returns the item at the top of a stack without removing it.
The stack operation that removes the item at the top of a stack and returns it.
The stack operation that adds a new item to the top of a stack.
A data structure in which items are added at one end, the rear, and removed from the other end, the front, so items leave in the order they arrived.
The end of a queue at which new items are added; in an array-based queue, a pointer holds the index of the last item.
A data structure in which items are added and removed at one end only, called the top, so the most recently added item is always the first removed.
The error that occurs when a program tries to push an item onto a stack that is already full.
The error that occurs when a program tries to pop or peek an item from an empty stack.
A data structure whose size is fixed when it is created and cannot change while the program runs.
A variable holding the index of the item currently at the top of an array-based stack, or −1 when the stack is empty.
Visiting every element of a data structure exactly once, in a systematic order, usually to read or process each one.
An ordered, immutable sequence in Python, written in round brackets, whose elements can be read by index but whose length and contents cannot change after creation.
An array whose elements are themselves arrays of equal length, used to store a table of rows and columns; each element is accessed with a row index and a column index.
YouTubeEvery value a running program uses lives in random-access memory (RAM), which is a long row of numbered storage locations called addresses. A single variable reserves enough locations for one value of its data type: four bytes for a Java int, for example. A data structure reserves space for many values and adds a rule for finding each one.
Reserving that space is called memory allocation. The operating system (OS) and the language's runtime hand out free regions of RAM to programs and take them back when they are no longer needed; subtopic A1.3, Operating systems and control systems, describes this as memory management. A data structure can receive all of its memory once, when it is created, or request more while the program runs, and this difference divides data structures into static and dynamic ones.
A static data structure has a size that is fixed when it is created. In Java, the statement int[] rainfall = new int[7]; reserves exactly seven int slots, and the array can never hold an eighth. The program can change the contents of the seven slots as often as it likes; only the number of slots is fixed.
The slots of a static array sit next to each other in one contiguous block of memory, in index order. Because every element has the same size, the address of any element can be calculated in one step from the start of the block, without searching:
The memory diagram of rainfall shows seven 4-byte integers stored from address 1000. Element 3 is at , and element 6 would be at . The calculation takes the same time whether the array has 7 elements or 7 million, which is why reading or writing any element by its index takes constant time, O(1).
![A seven-element integer array rainfall stored in one contiguous block between other data. Indices 0 to 6 hold 12, 0, 5, 21, 3, 0, 8 at addresses 1000 to 1024 in steps of 4. rainfall[3] is at 1000 + 3 × 4 = 1012.](/_next/image?url=https%3A%2F%2Fmedia.augment-education.com%2Fnotes%2F2026%2F10%2Fe823744d-5863-40fe-b572-888987429fa4.webp&w=3840&q=75)
The same diagram explains the main limitation. Memory on either side of the block may already belong to other data, so the array cannot be stretched where it is. A program that needs more room must create a new, larger array and copy the elements across itself.
A dynamic data structure grows and shrinks while the program runs. A Python list starts empty and accepts as many append calls as memory allows; a Java ArrayList does the same with add. The program does not need to know in advance how many items will arrive.
There are two ways to build a dynamic structure. A dynamic array (also called a resizable array) keeps its elements in one contiguous block, like a static array, but replaces the block with a larger one when it fills up. A linked structure stores each item in a separate piece of memory together with a reference to the next item, so it can grow one item at a time anywhere in memory; linked lists are HL content in subtopic B4.1. Java's ArrayList and Python's list are both dynamic arrays, which keeps their indexing as fast as a static array's.
A dynamic array allocates more slots than it currently needs. The number of slots in the block is its capacity; the number of items actually stored is its size. While the size is below the capacity, adding an item only writes it into the next spare slot.
When an item arrives and the array is full, the array performs a resize. It allocates a new block, copies every existing element into it, stores the new item and releases the old block. The resize diagram shows a full block of capacity 4 being replaced by one of capacity 6. The new capacity is the old capacity plus half of it, which is the rule Java's ArrayList uses.

The growth factor matters a great deal. If the array grew by one slot each time, every single addition would copy the whole array, and appending items would copy elements in total. Growing by a fixed proportion makes resizes rare, because each one creates many spare slots. The program count_copies simulates both rules for an array that starts empty: one slot at a time, and Java's rule (start at 10, then add half the capacity).
def count_copies(n, grow_by_half):
"""Append n items to an empty dynamic array; return (resizes, copies)."""
capacity = 0
resizes = 0
copies = 0
for size in range(n): # size = items already stored
if size == capacity: # array full: allocate a bigger block
copies = copies + size # every stored item is copied across
resizes = resizes + 1
if not grow_by_half:
capacity = capacity + 1
elif capacity == 0:
capacity = 10
else:
capacity = capacity + capacity // 2
return resizes, copies
print("items +1 slot: resizes, copies x1.5: resizes, copies")
for n in [10, 100, 1000, 10000]:
print(n, count_copies(n, False), count_copies(n, True))public class Growth {
// Append n items to an empty dynamic array; return {resizes, copies}.
static long[] countCopies(int n, boolean growByHalf) {
int capacity = 0;
long resizes = 0;
long copies = 0;
for (int size = 0; size < n; size++) { // size = items already stored
if (size == capacity) { // array full: allocate a bigger block
copies = copies + size; // every stored item is copied across
resizes = resizes + 1;
if (!growByHalf) {
capacity = capacity + 1;
} else if (capacity == 0) {
capacity = 10;
} else {
capacity = capacity + capacity / 2;
}
}
}
return new long[] {resizes, copies};
}
public static void main(String[] args) {
System.out.println("items +1 slot: resizes, copies x1.5: resizes, copies");
int[] sizes = {10, 100, 1000, 10000};
for (int n : sizes) {
long[] a = countCopies(n, false);
long[] b = countCopies(n, true);
System.out.println(n + " (" + a[0] + ", " + a[1] + ") (" + b[0] + ", " + b[1] + ")");
}
}
}Both versions print the same table:
items +1 slot: resizes, copies x1.5: resizes, copies
10 (10, 45) (1, 0)
100 (100, 4950) (7, 202)
1000 (1000, 499500) (13, 2456)
10000 (10000, 49995000) (19, 28096)For 10,000 items, growing one slot at a time copies almost 50 million elements, while growing by half copies 28,096: fewer than three copies per item on average. Most additions cost a single write and only an occasional one triggers a large copy, so the average cost of an addition stays constant even though a single addition can take time. The cost is extra memory, because just after a resize up to a third of the new block is spare slots that hold nothing.
Neither family is better in every situation. A static structure is the right choice when the number of items is known and fixed: seven days in a week, 64 squares on a chessboard, 12 monthly totals. A dynamic structure suits data whose amount is unknown or changes: items in a shopping basket, songs in a playlist, sensor readings logged until someone presses stop.
Factor | Static (fixed-size array) | Dynamic ( |
|---|---|---|
Size | Fixed when created | Grows and shrinks at run time |
Memory allocation | One block, allocated once | Block replaced by a larger one when full; spare capacity reserved |
Access by index | : address calculated directly | for a dynamic array, same calculation |
Adding an item | Impossible once full: the program must create a new array and copy | Usually one write; occasionally a resize that copies every element |
Memory use | Exactly the slots requested; wasted if many stay unused | Extra spare slots, plus bookkeeping such as the size and capacity |
Risk | Overflow or index errors when more data arrives than planned | Higher memory use; a resize pauses briefly while copying |
Typical use | Days of the week, board games, image pixels, fixed tables | Baskets, playlists, search results, logs of unknown length |
Both kinds store elements of a declared type, both are indexed from 0, and a dynamic array uses a static array internally. The difference lies in who handles growth: with a static array the programmer must notice that it is full and copy the data, while a dynamic structure does it automatically at a small, mostly hidden cost.
Java's ArrayList and Python's list are both dynamic arrays: each object keeps its elements in an ordinary fixed-size array that it replaces when full. The public source code of both languages shows the exact growth rules. Java's ArrayList documents a default initial capacity of 10. When an element is added to a full list, its private grow method computes a preferred new capacity of oldCapacity + (oldCapacity >> 1): the shift right by one bit halves the old capacity, so the list grows by about 50%. The elements are copied into the new array with Arrays.copyOf, and the old array becomes garbage that the Java runtime reclaims.
CPython, the standard Python interpreter, describes its lists as "variable-length arrays, not Lisp-style linked lists": one contiguous array of references to objects. When a list must grow, list_resize allocates about one eighth more than the new size, plus a small constant: the formula is newsize + (newsize >> 3) + 6, rounded down to a multiple of 4. Because a Python list stores references rather than the values themselves, one list can hold an integer, a string and another list side by side, and its index arithmetic still works because every reference is the same size.
The two growth rules give quite different capacity sequences for a list that starts empty and keeps growing:
Resize number | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
Java | 10 | 15 | 22 | 33 | 49 | 73 | 109 |
CPython list capacity | 4 | 8 | 16 | 24 | 32 | 40 | 52 |
Sources: OpenJDK source file java/util/ArrayList.java (DEFAULT_CAPACITY and the grow method); CPython source file Objects/listobject.c and the Python Design and History FAQ, "How are lists implemented in CPython?"; all read October 2026.
The two designs make different trade-offs. Java's 50% growth means fewer resizes but up to a third of the block unused after a resize; CPython's smaller over-allocation wastes less memory per list but resizes more often. Both choices keep the average cost of appending constant, and both keep indexing, which a linked structure cannot offer. Inserting at the front is slow in both, because every element must shift one place.
An array stores a fixed number of elements of one data type under one name. Each element is reached by its index, counting from 0, so an array of length has indices 0 to .
Each stored value is called an element. The index says where an element is; the element is the value found there. In rainfall[3] = 21, 3 is the index and 21 is the element.
The two course languages create fixed-size arrays differently. Java has true static arrays: int[] rainfall = new int[7]; creates seven integers, each set to a default value (0 for numbers, false for boolean, null for objects such as String), and rainfall.length gives 7. Python has no built-in static array in this sense. A list created at a fixed size with rainfall = [0] * 7 and never appended to or removed from behaves like one, and that is how Python programs model a static array. Python also offers the tuple for a sequence that must never change.
A Java array is still created at run time, and in Java every array is stored on the heap. What makes it static is that its length can never change once new has run; immutability is a different idea, because the elements of a Java array can be overwritten freely.
Reading or writing one element uses its index in square brackets: rainfall[3] reads the fourth element and rainfall[3] = 21 replaces it. Many tasks need every element in turn, such as a total, an average, a maximum or a count, and visiting each element in order is called traversal.
Two loops traverse an array. A counted loop runs an index from 0 to the last index, which gives both the position and the value; a for-each loop gives each value directly but not its index. The program for a week of rainfall uses both: a counted loop to find the total and the wettest day (which needs the index), and a for-each loop to count the dry days.
# A fixed-size "array" of daily rainfall (mm) for one week
rainfall = [0] * 7 # seven elements, all 0
rainfall[0] = 12
rainfall[1] = 0
rainfall[2] = 5
rainfall[3] = 21
rainfall[4] = 3
rainfall[5] = 0
rainfall[6] = 8
# Traverse with a counted loop: total, maximum and its index
total = 0
highest = rainfall[0]
highest_day = 0
for i in range(7):
total = total + rainfall[i]
if rainfall[i] > highest:
highest = rainfall[i]
highest_day = i
# Traverse with a for-each loop: count the dry days
dry_days = 0
for amount in rainfall:
if amount == 0:
dry_days = dry_days + 1
print("Total:", total)
print("Average:", total / 7)
print("Wettest day index:", highest_day, "with", highest)
print("Dry days:", dry_days)public class Rainfall {
public static void main(String[] args) {
int[] rainfall = new int[7]; // seven elements, all 0
rainfall[0] = 12;
rainfall[1] = 0;
rainfall[2] = 5;
rainfall[3] = 21;
rainfall[4] = 3;
rainfall[5] = 0;
rainfall[6] = 8;
// Traverse with a counted loop: total, maximum and its index
int total = 0;
int highest = rainfall[0];
int highestDay = 0;
for (int i = 0; i < rainfall.length; i++) {
total = total + rainfall[i];
if (rainfall[i] > highest) {
highest = rainfall[i];
highestDay = i;
}
}
// Traverse with a for-each loop: count the dry days
int dryDays = 0;
for (int amount : rainfall) {
if (amount == 0) {
dryDays = dryDays + 1;
}
}
System.out.println("Total: " + total);
System.out.println("Average: " + (double) total / rainfall.length);
System.out.println("Wettest day index: " + highestDay + " with " + highest);
System.out.println("Dry days: " + dryDays);
}
}Both programs print the same results:
Total: 49
Average: 7.0
Wettest day index: 3 with 21
Dry days: 2The maximum starts as the first element, not as 0, so the code also works when every value is negative. The total, maximum and count are all written as loops rather than with Python's sum, max or len, which shows the work those built-ins do and gives code that runs where they are not available. The Java version casts total to double before dividing; without the cast, 49 / 7 would use integer division and any fractional part would be lost.
Python's built-in sequence type is the list. It is ordered, indexed from 0 and dynamic, and it can hold values of any type.
Java separates the two ideas. Its arrays are static, and the class ArrayList from the package java.util provides the dynamic version.
The type in angle brackets says what the ArrayList holds. It must be a class, so whole numbers are stored as Integer and decimals as Double; Java converts between int and Integer automatically (called autoboxing), so list.add(5) works. The table pairs the common operations in the two languages.
Operation | Python list | Java |
|---|---|---|
Create empty |
|
|
Add to the end |
|
|
Insert at index |
|
|
Read element |
|
|
Replace element |
|
|
Remove at index |
|
|
Remove first matching value |
|
|
Number of elements |
|
|
Contains value? |
|
|
Position of value |
|
|
The playlist program uses each kind of operation once and prints the list after each change.
playlist = [] # empty list: size 0
playlist.append("Intro") # add to the end
playlist.append("Skyline")
playlist.append("Outro")
print(playlist, len(playlist))
playlist.insert(1, "Rain") # insert at index 1; later items shift right
print(playlist)
playlist[2] = "Skyline (live)" # update the element at index 2
print(playlist[2])
playlist.remove("Intro") # remove the first matching value
removed = playlist.pop(0) # remove and return the item at index 0
print(removed, playlist)
print("Outro" in playlist, playlist.index("Outro"))import java.util.ArrayList;
public class Playlist {
public static void main(String[] args) {
ArrayList<String> playlist = new ArrayList<String>(); // empty: size 0
playlist.add("Intro"); // add to the end
playlist.add("Skyline");
playlist.add("Outro");
System.out.println(playlist + " " + playlist.size());
playlist.add(1, "Rain"); // insert at index 1; later items shift right
System.out.println(playlist);
playlist.set(2, "Skyline (live)"); // update the element at index 2
System.out.println(playlist.get(2));
playlist.remove("Intro"); // remove the first matching value
String removed = playlist.remove(0); // remove and return the item at index 0
System.out.println(removed + " " + playlist);
System.out.println(playlist.contains("Outro") + " " + playlist.indexOf("Outro"));
}
}The Java version prints [Intro, Skyline, Outro] 3 and the same values without quotation marks, ending true 1.
The Python version prints:
['Intro', 'Skyline', 'Outro'] 3
['Intro', 'Rain', 'Skyline', 'Outro']
Skyline (live)
Rain ['Skyline (live)', 'Outro']
True 1After remove("Intro"), "Rain" moves to index 0, so pop(0) removes "Rain". Every removal or insertion changes the index of each element after it, which is the source of several common bugs.
Adding to the end of a dynamic array is cheap, but inserting in the middle is not. The insertion diagram shows insert(1, 55) on an array holding 40, 70 and 90: to open a gap at index 1, the array moves 90 one place right, then 70, and only then writes 55. The moves start from the end; starting from the gap would overwrite 90 with 70 before 90 had been moved.

Removing at an index does the reverse: every later element moves one place left to close the gap. Both operations take time proportional to the number of elements that move, so inserting or removing at the front of a list of elements is , while adding or removing at the end is usually . The functions insert_at and remove_at do the shifting by hand on a fixed block of six slots, with a separate counter size for the slots in use: the same work that insert and pop hide.
CAPACITY = 6
items = [0] * CAPACITY # fixed-size block
size = 0 # how many slots are in use
def insert_at(index, value):
global size
if size == CAPACITY:
print("Full: cannot insert", value)
return
for i in range(size, index, -1): # shift right, starting at the end
items[i] = items[i - 1]
items[index] = value
size = size + 1
def remove_at(index):
global size
value = items[index]
for i in range(index, size - 1): # shift left, starting at the gap
items[i] = items[i + 1]
items[size - 1] = 0 # clear the old last slot
size = size - 1
return value
insert_at(0, 40)
insert_at(1, 70)
insert_at(2, 90)
insert_at(1, 55) # 70 and 90 move one place right
print(items, size)
print(remove_at(0), items, size)public class Shift {
static final int CAPACITY = 6;
static int[] items = new int[CAPACITY]; // fixed-size block
static int size = 0; // how many slots are in use
static void insertAt(int index, int value) {
if (size == CAPACITY) {
System.out.println("Full: cannot insert " + value);
return;
}
for (int i = size; i > index; i--) { // shift right, starting at the end
items[i] = items[i - 1];
}
items[index] = value;
size = size + 1;
}
static int removeAt(int index) {
int value = items[index];
for (int i = index; i < size - 1; i++) { // shift left, starting at the gap
items[i] = items[i + 1];
}
items[size - 1] = 0; // clear the old last slot
size = size - 1;
return value;
}
public static void main(String[] args) {
insertAt(0, 40);
insertAt(1, 70);
insertAt(2, 90);
insertAt(1, 55); // 70 and 90 move one place right
System.out.println(java.util.Arrays.toString(items) + " " + size);
int removed = removeAt(0);
System.out.println(removed + " " + java.util.Arrays.toString(items) + " " + size);
}
}Arrays.toString is used only to print the array.
[40, 55, 70, 90, 0, 0] 4
40 [55, 70, 90, 0, 0, 0] 3The Python functions use global size because they assign to the global variable size; the Java version uses a static field for the same purpose.
A frequent task is removing every element that fails a test, such as every mark below 40. Removing while stepping forward through a list skips elements: after a removal, the next element slides into the current index, and the loop then moves past it without checking it.
marks = [45, 30, 28, 72, 15, 60]
# Wrong: removing while moving forward skips the item after each removal
wrong = marks[:] # work on a copy
i = 0
while i < len(wrong):
if wrong[i] < 40:
wrong.pop(i)
i = i + 1
print("Forward loop:", wrong)
# Right: walk backwards, so a removal never moves an item not yet checked
right = marks[:]
for i in range(len(right) - 1, -1, -1):
if right[i] < 40:
right.pop(i)
print("Backward loop:", right)import java.util.ArrayList;
public class RemoveBug {
public static void main(String[] args) {
int[] data = {45, 30, 28, 72, 15, 60};
// Wrong: removing while moving forward skips the item after each removal
ArrayList<Integer> wrong = new ArrayList<Integer>();
for (int m : data) {
wrong.add(m);
}
for (int i = 0; i < wrong.size(); i++) {
if (wrong.get(i) < 40) {
wrong.remove(i);
}
}
System.out.println("Forward loop: " + wrong);
// Right: walk backwards, so a removal never moves an item not yet checked
ArrayList<Integer> right = new ArrayList<Integer>();
for (int m : data) {
right.add(m);
}
for (int i = right.size() - 1; i >= 0; i--) {
if (right.get(i) < 40) {
right.remove(i);
}
}
System.out.println("Backward loop: " + right);
}
}Forward loop: [45, 28, 72, 60]
Backward loop: [45, 72, 60]The forward loop removes 30 at index 1, 28 slides into index 1, and the loop moves on to index 2, so 28 is never tested. Walking backwards avoids this because a removal only moves elements that have already been checked. Another safe approach is to build a new list containing only the elements to keep. In Python, marks[:] makes a copy of the list so that the original stays unchanged.
Many kinds of data come as a table: scores for several students on several tests, seats in rows of a cinema, pixels in an image, squares on a board. A two-dimensional array stores such a table as rows and columns, and each element needs two indices.
By convention the first index selects the row and the second selects the column: scores[r][c]. The grid diagram shows three students (rows 0 to 2) and four tests (columns 0 to 3). scores[1] is the whole of row 1, the list [90, 84, 77, 95], and scores[1][3] is the single element 95 at the end of that row.
![A 3 by 4 grid scores with row indices 0 to 2 (students) and column indices 0 to 3 (tests). Rows: 62 75 58 81; 90 84 77 95; 48 55 70 63. Row 1 is shaded as scores[1]; the cell scores[1][3] = 95 is marked.](/_next/image?url=https%3A%2F%2Fmedia.augment-education.com%2Fnotes%2F2026%2F10%2Fadefba05-6d5e-4b79-a28c-0441006bf803.webp&w=3840&q=75)
In Java, int[][] scores = new int[3][4]; creates three rows of four integers, all 0, and the values can also be written directly between braces. scores.length is the number of rows (3) and scores[0].length the number of columns (4). Python has no separate 2D type: a 2D list is a list of lists, and len(scores) and len(scores[0]) give the rows and columns.
Visiting every element needs a nested loop: an outer loop over the rows and an inner loop over the columns. Processing a whole row before moving to the next is called row-major order, and it suits per-row results such as each student's total. Swapping the loops gives column-by-column order, which suits per-column results such as each test's average.
# scores[student][test]: 3 rows (students) by 4 columns (tests)
scores = [
[62, 75, 58, 81],
[90, 84, 77, 95],
[48, 55, 70, 63],
]
ROWS = 3
COLS = 4
# Row by row (row-major): each student's total
for r in range(ROWS):
total = 0
for c in range(COLS):
total = total + scores[r][c]
print("Student", r, "total", total)
# Column by column: each test's average
for c in range(COLS):
total = 0
for r in range(ROWS):
total = total + scores[r][c]
print("Test", c, "average", total / ROWS)
# Whole grid: highest score and where it is
best = scores[0][0]
best_r = 0
best_c = 0
for r in range(ROWS):
for c in range(COLS):
if scores[r][c] > best:
best = scores[r][c]
best_r = r
best_c = c
print("Highest", best, "at row", best_r, "column", best_c)public class Grid {
public static void main(String[] args) {
// scores[student][test]: 3 rows (students) by 4 columns (tests)
int[][] scores = {
{62, 75, 58, 81},
{90, 84, 77, 95},
{48, 55, 70, 63}
};
int rows = scores.length; // 3
int cols = scores[0].length; // 4
// Row by row (row-major): each student's total
for (int r = 0; r < rows; r++) {
int total = 0;
for (int c = 0; c < cols; c++) {
total = total + scores[r][c];
}
System.out.println("Student " + r + " total " + total);
}
// Column by column: each test's average
for (int c = 0; c < cols; c++) {
int total = 0;
for (int r = 0; r < rows; r++) {
total = total + scores[r][c];
}
System.out.println("Test " + c + " average " + (double) total / rows);
}
// Whole grid: highest score and where it is
int best = scores[0][0];
int bestR = 0;
int bestC = 0;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (scores[r][c] > best) {
best = scores[r][c];
bestR = r;
bestC = c;
}
}
}
System.out.println("Highest " + best + " at row " + bestR + " column " + bestC);
}
}Student 0 total 276
Student 1 total 346
Student 2 total 236
Test 0 average 66.66666666666667
Test 1 average 71.33333333333333
Test 2 average 68.33333333333333
Test 3 average 79.66666666666667
Highest 95 at row 1 column 3The position of total = 0 decides what is totalled. Placed inside the outer loop, the total restarts for every row (or column); placed before both loops, it would accumulate the whole grid. In every case the indices stay in the order scores[r][c], whichever loop is outside.
The program row_max finds something about each row of a small grid; its Java version, RowMax, is identical in structure.
grid = [[5, 8, 2], [7, 3, 9]]
for r in range(2):
biggest = grid[r][0]
for c in range(1, 3):
if grid[r][c] > biggest:
biggest = grid[r][c]
print(r, biggest)public class RowMax {
public static void main(String[] args) {
int[][] grid = {{5, 8, 2}, {7, 3, 9}};
for (int r = 0; r < 2; r++) {
int biggest = grid[r][0];
for (int c = 1; c < 3; c++) {
if (grid[r][c] > biggest) {
biggest = grid[r][c];
}
}
System.out.println(r + " " + biggest);
}
}
}Python offers a tempting shortcut for an empty grid, [[0] * 4] * 3, but it is wrong. The inner expression makes one row; multiplying the outer list by 3 copies the reference to that row three times. The aliasing diagram contrasts the two results: shared has three slots pointing at the same row, so a change to shared[0][1] appears in every row, while board has three separate rows.
![Left: list shared made by [[0] * 4] * 3 has three slots all pointing to one row 0 9 0 0. Right: list board has three slots each pointing to its own row; only row 0 holds the 9. Three references to one row versus three rows.](/_next/image?url=https%3A%2F%2Fmedia.augment-education.com%2Fnotes%2F2026%2F10%2Fc48d1f31-186b-43c8-beab-4855c41b8e6c.webp&w=3840&q=75)
shared = [[0] * 4] * 3 # one row object, referenced three times
shared[0][1] = 9
print(shared)
board = [] # three separate row objects
for r in range(3):
board.append([0] * 4)
board[0][1] = 9
print(board)import java.util.Arrays;
public class Alias {
public static void main(String[] args) {
int[] row = new int[4];
int[][] shared = {row, row, row}; // one row array, referenced three times
shared[0][1] = 9;
System.out.println(Arrays.deepToString(shared));
int[][] board = new int[3][4]; // three separate row arrays
board[0][1] = 9;
System.out.println(Arrays.deepToString(board));
}
}Java's new int[3][4] always creates separate rows; the same fault appears only if a program deliberately reuses one row array, as shared does here.
[[0, 9, 0, 0], [0, 9, 0, 0], [0, 9, 0, 0]]
[[0, 9, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]The fault is easy to miss because the shortcut looks correct when printed before any change. It comes from how lists store references to objects rather than the objects themselves, the same reason a dynamic array can hold values of mixed types.
A stack holds items in a pile and allows access to one end only, called the top. A new item goes on top, and the only item that can be removed is the one on top: the one added most recently. A pile of plates in a canteen behaves this way, and so does the undo feature of a text editor, which reverses the most recent change first.
The order a stack enforces has its own name, LIFO. Items leave in the reverse of the order they arrived: push A, B and C and they come back as C, B and A.
The term "stack" also names the call stack that a running program uses to keep track of subprogram calls. The call stack works on exactly the LIFO rule but is managed by the language, not written by the programmer; it is HL content in subtopic B2.4, Programming algorithms.
A stack is defined by what can be done to it. Four operations are enough for almost every use, and each acts only on the top.
The opposite operation removes the top item and hands it back to the program.
Sometimes a program only needs to know what is on top, without changing the stack.
Before popping or peeking, a program must know whether there is anything to remove.
An array-based stack keeps track of the top with a single integer variable that holds the index of the top item. On an empty stack it holds −1, one below the first index.
The stack sequence diagram follows a stack of capacity 3 through six operations. Each push moves the top pointer up one place and writes the new item there; each pop reads the item at the top pointer and moves the pointer down; peek reads without moving anything.

The same sequence can be recorded as a table with one row per operation, a compact written record of every change. The contents are listed bottom to top.
Operation | Returns | Stack contents (bottom → top) |
|
|---|---|---|---|
(start) | empty | −1 | |
| 3 | 0 | |
| 3, 7 | 1 | |
| 3, 7, 5 | 2 | |
| 5 | 3, 7 | 1 |
| 7 | 3, 7 | 1 |
| 3, 7, 8 | 2 |
After the pop, the 5 may still be stored in slot 2 of the array, but it is no longer part of the stack: the top pointer marks where the stack ends, and the next push overwrites that slot with 8.
An array-based stack has a fixed capacity, so two things can go wrong. Pushing onto a full stack has nowhere to put the item, and popping an empty stack has nothing to return. A correct implementation checks for both before acting.
The empty case has its own name.
The Stack class implements all four operations on a fixed-size array, plus is_full to detect overflow. It raises an exception on overflow or underflow rather than returning a wrong value, so the calling code can decide what to do.
class Stack:
def __init__(self, capacity):
self.items = [None] * capacity # fixed-size block
self.capacity = capacity
self.top = -1 # index of the top item; -1 = empty
def is_empty(self):
return self.top == -1
def is_full(self):
return self.top == self.capacity - 1
def push(self, value):
if self.is_full():
raise OverflowError("stack overflow")
self.top = self.top + 1
self.items[self.top] = value
def pop(self):
if self.is_empty():
raise IndexError("stack underflow")
value = self.items[self.top]
self.items[self.top] = None # optional: clear the slot
self.top = self.top - 1
return value
def peek(self):
if self.is_empty():
raise IndexError("peek at empty stack")
return self.items[self.top]
s = Stack(3)
s.push(3)
s.push(7)
s.push(5)
print("pop", s.pop())
print("peek", s.peek())
s.push(8)
try:
s.push(2)
except OverflowError as error:
print(error)
while not s.is_empty():
print("pop", s.pop())
try:
s.pop()
except IndexError as error:
print(error)class Stack {
private int[] items; // fixed-size block
private int top; // index of the top item; -1 = empty
Stack(int capacity) {
items = new int[capacity];
top = -1;
}
boolean isEmpty() {
return top == -1;
}
boolean isFull() {
return top == items.length - 1;
}
void push(int value) {
if (isFull()) {
throw new IllegalStateException("stack overflow");
}
top = top + 1;
items[top] = value;
}
int pop() {
if (isEmpty()) {
throw new IllegalStateException("stack underflow");
}
int value = items[top];
top = top - 1;
return value;
}
int peek() {
if (isEmpty()) {
throw new IllegalStateException("peek at empty stack");
}
return items[top];
}
}
public class StackDemo {
public static void main(String[] args) {
Stack s = new Stack(3);
s.push(3);
s.push(7);
s.push(5);
System.out.println("pop " + s.pop());
System.out.println("peek " + s.peek());
s.push(8);
try {
s.push(2);
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
}
while (!s.isEmpty()) {
System.out.println("pop " + s.pop());
}
try {
s.pop();
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
}
}
}The Java class holds int values, so pop cannot clear a slot to null; the old value stays in the array but lies above the top pointer and is overwritten by the next push.
Both programs print:
pop 5
peek 7
stack overflow
pop 8
pop 7
pop 3
stack underflowThe order of the two lines inside push matters. The pointer moves up first and the item is written at the new position; in pop, the item is read first and the pointer moves down afterwards. Reversing either pair overwrites the top item or returns the wrong one.
YouTubePush, pop, peek and isEmpty each read or change one slot and one pointer, so each takes constant time, , however many items the stack holds. No element ever moves, because nothing is inserted or removed anywhere except the top. A program that only ever needs the most recent item therefore avoids the shifting of a list insertion or removal at the front.
Memory depends on how the stack is built. An array-based stack reserves its full capacity when it is created, so a stack of capacity 1,000 holding 3 items still occupies 1,000 slots, and a 1,001st push overflows. A dynamic stack, built on a dynamic array or a linked structure, grows as items arrive: its memory is roughly proportional to the number of items, and it overflows only when the computer runs out of memory. The trade-off is the same one as between static and dynamic arrays: predictable, fixed memory with a hard limit, or flexible memory with occasional resizing (or, for a linked stack, an extra reference stored with every item).
Both course languages provide ready-made stacks. In Python, a list is used directly: append pushes, pop() with no argument pops from the end, stack[-1] peeks and len(stack) == 0 tests for empty, and all of these are because they work at the end of the list. In Java, the ArrayDeque class provides push, pop, peek and isEmpty. Java also has an older class called Stack, which still works but is no longer recommended.
from collections import deque
# Undo history: a stack (list: append = push, pop() = pop, [-1] = peek)
history = []
history.append("type 'Hi'")
history.append("bold")
history.append("type '!'")
print("undo:", history.pop())
print("next undo would be:", history[-1])
# Print queue: a queue (deque: append = enqueue, popleft() = dequeue, [0] = front)
jobs = deque()
jobs.append("essay.pdf")
jobs.append("photo.png")
jobs.append("ticket.pdf")
print("printing:", jobs.popleft())
print("next to print:", jobs[0])
print("queue empty?", len(jobs) == 0)import java.util.ArrayDeque;
public class Builtins {
public static void main(String[] args) {
// Undo history: a stack (push, pop, peek)
ArrayDeque<String> history = new ArrayDeque<String>();
history.push("type 'Hi'");
history.push("bold");
history.push("type '!'");
System.out.println("undo: " + history.pop());
System.out.println("next undo would be: " + history.peek());
// Print queue: a queue (offer = enqueue, poll = dequeue, peek = front)
ArrayDeque<String> jobs = new ArrayDeque<String>();
jobs.offer("essay.pdf");
jobs.offer("photo.png");
jobs.offer("ticket.pdf");
System.out.println("printing: " + jobs.poll());
System.out.println("next to print: " + jobs.peek());
System.out.println("queue empty? " + jobs.isEmpty());
}
}ArrayDeque adds at its head with push and at its tail with offer, and both pop and poll remove from the head, so the same class acts as a stack or as a queue depending on which methods are called.
undo: type '!'
next undo would be: bold
printing: essay.pdf
next to print: photo.png
queue empty? FalseIn the print-queue half of the program, the oldest job leaves first rather than the newest, which is the behaviour of a queue. The Java version prints false in lower case.
A stack is the right choice whenever a problem needs the most recent unfinished item first. That pattern appears in many places:
Undo in editors. Each change is pushed; undo pops the most recent change and reverses it. A second stack often holds undone changes so that redo can push them back.
Browser back button. Each page visited is pushed; Back pops the current page and shows the page visited before it.
Reversing data. Pushing a sequence and then popping everything returns it in reverse order.
Matching brackets. Each opening bracket is pushed; each closing bracket must match the bracket popped from the top.
Evaluating expressions. Calculators and compilers use stacks to hold numbers and operators until they can be combined in the right order.
Backtracking. A maze solver pushes each junction it passes, and pops back to the most recent junction when it reaches a dead end (depth-first search).
Subprogram calls. The language keeps a call stack: a call pushes a record of where to return, and a return pops it.
Bracket matching shows why only the top matters. In f(x[2)] the ] and ) are each present, but the ) arrives while [ is still open, so the brackets cross. The function is_balanced pushes every opener and checks each closer against the most recent opener.
def is_balanced(text):
stack = [] # Python list used as a stack
for ch in text:
if ch == "(" or ch == "[" or ch == "{":
stack.append(ch) # push the opener
elif ch == ")" or ch == "]" or ch == "}":
if len(stack) == 0: # closer with nothing open
return False
opener = stack.pop() # pop the most recent opener
if (opener == "(" and ch != ")") or \
(opener == "[" and ch != "]") or \
(opener == "{" and ch != "}"):
return False # wrong kind of closer
return len(stack) == 0 # anything left open?
for test in ["a[i] = (b + c) * {d}", "f(x[2)]", "((a + b)", "g(h[i])}"]:
print(test, is_balanced(test))import java.util.ArrayDeque;
public class Brackets {
static boolean isBalanced(String text) {
ArrayDeque<Character> stack = new ArrayDeque<Character>(); // used as a stack
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch == '(' || ch == '[' || ch == '{') {
stack.push(ch); // push the opener
} else if (ch == ')' || ch == ']' || ch == '}') {
if (stack.isEmpty()) { // closer with nothing open
return false;
}
char opener = stack.pop(); // pop the most recent opener
if ((opener == '(' && ch != ')')
|| (opener == '[' && ch != ']')
|| (opener == '{' && ch != '}')) {
return false; // wrong kind of closer
}
}
}
return stack.isEmpty(); // anything left open?
}
public static void main(String[] args) {
String[] tests = {"a[i] = (b + c) * {d}", "f(x[2)]", "((a + b)", "g(h[i])}"};
for (String test : tests) {
System.out.println(test + " " + isBalanced(test));
}
}
}ArrayDeque<Character> stores char values as Character objects, in the same way that ArrayList<Integer> stores int values. The Java version prints true and false in lower case.
a[i] = (b + c) * {d} True
f(x[2)] False
((a + b) False
g(h[i])} FalseThe three failing strings fail in three different ways: a closer that does not match the top (f(x[2)]), an opener left on the stack at the end (((a + b)), and a closer arriving when the stack is empty (g(h[i])}). The empty check before each pop is what prevents underflow in the third case.
A queue works like a line of people at a ticket counter. Newcomers join at the back, the person at the front is served next, and nobody jumps ahead. Items leave in the order they arrived, which makes a queue the fair choice wherever requests must be served in turn.
The order a queue enforces is the opposite of a stack's.
A queue has two ends with different jobs, so it needs a name for each and an operation for each.
Removing happens only at the other end.
The front is where the next item to leave waits; the operation of the same name reads it without removing it, just as peek does for a stack.
The other end has its own pointer.
The fourth operation, isEmpty, is the same as for a stack: it reports whether the queue holds any items, and a program checks it before dequeuing. Dequeuing an empty queue is an underflow, and enqueuing onto a full fixed-size queue is an overflow, exactly as for a stack.
The simplest array-based queue keeps a front pointer and a rear pointer that both move only to the right. Enqueue increases rear and writes the item there; dequeue reads the item at front and increases front. Both are , because no elements move.
The linear queue diagram shows the flaw. After four enqueues and two dequeues, slots 0 and 1 are empty, but rear can only move right. After two more enqueues, rear reaches the last slot and the queue reports that it is full, although two of its six slots are free.

There are two simple fixes, and both have a cost. The first is to shift every remaining item one place left after each dequeue, so the front is always at index 0; this reuses the space but makes every dequeue . The second is to let the pointers wrap round to the start of the array, which keeps every operation and reuses every slot. That second design is the circular queue.
A circular queue treats the array as a ring: the slot after the last one is slot 0. When rear reaches the end and slot 0 is free, the next item goes into slot 0.
The wrap is done with the modulo operator. With capacity , the next index after is : for capacity 4, and . The circular queue diagram shows a queue of capacity 4 after the sequence used in the program that follows. P5 and P6 sit in slots 0 and 1 because the rear pointer has wrapped round, but the front pointer is at slot 2, so P3 leaves first.

Once the pointers wrap, comparing them no longer shows whether the queue is empty or full: in both cases the rear sits just behind the front. The usual solution is a third variable, count, which holds the number of items. The queue is empty when count is 0 and full when count equals the capacity.
class CircularQueue:
def __init__(self, capacity):
self.items = [None] * capacity # fixed-size block
self.capacity = capacity
self.front = 0 # index of the first item
self.rear = -1 # index of the last item
self.count = 0 # number of items stored
def is_empty(self):
return self.count == 0
def is_full(self):
return self.count == self.capacity
def enqueue(self, value):
if self.is_full():
raise OverflowError("queue full")
self.rear = (self.rear + 1) % self.capacity # wrap round to 0
self.items[self.rear] = value
self.count = self.count + 1
def dequeue(self):
if self.is_empty():
raise IndexError("queue empty")
value = self.items[self.front]
self.items[self.front] = None
self.front = (self.front + 1) % self.capacity # wrap round to 0
self.count = self.count - 1
return value
def peek(self): # the "front" operation
if self.is_empty():
raise IndexError("queue empty")
return self.items[self.front]
q = CircularQueue(4)
q.enqueue("P1")
q.enqueue("P2")
q.enqueue("P3")
print("dequeue", q.dequeue())
print("dequeue", q.dequeue())
q.enqueue("P4")
q.enqueue("P5") # rear wraps from 3 to 0
q.enqueue("P6")
print(q.items, "front", q.front, "rear", q.rear, "count", q.count)
try:
q.enqueue("P7")
except OverflowError as error:
print(error)
print("front item", q.peek())
while not q.is_empty():
print("dequeue", q.dequeue())import java.util.Arrays;
class CircularQueue {
private String[] items; // fixed-size block
private int front; // index of the first item
private int rear; // index of the last item
private int count; // number of items stored
CircularQueue(int capacity) {
items = new String[capacity];
front = 0;
rear = -1;
count = 0;
}
boolean isEmpty() {
return count == 0;
}
boolean isFull() {
return count == items.length;
}
void enqueue(String value) {
if (isFull()) {
throw new IllegalStateException("queue full");
}
rear = (rear + 1) % items.length; // wrap round to 0
items[rear] = value;
count = count + 1;
}
String dequeue() {
if (isEmpty()) {
throw new IllegalStateException("queue empty");
}
String value = items[front];
items[front] = null;
front = (front + 1) % items.length; // wrap round to 0
count = count - 1;
return value;
}
String peek() { // the "front" operation
if (isEmpty()) {
throw new IllegalStateException("queue empty");
}
return items[front];
}
String state() {
return Arrays.toString(items) + " front " + front + " rear " + rear + " count " + count;
}
}
public class QueueDemo {
public static void main(String[] args) {
CircularQueue q = new CircularQueue(4);
q.enqueue("P1");
q.enqueue("P2");
q.enqueue("P3");
System.out.println("dequeue " + q.dequeue());
System.out.println("dequeue " + q.dequeue());
q.enqueue("P4");
q.enqueue("P5"); // rear wraps from 3 to 0
q.enqueue("P6");
System.out.println(q.state());
try {
q.enqueue("P7");
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
}
System.out.println("front item " + q.peek());
while (!q.isEmpty()) {
System.out.println("dequeue " + q.dequeue());
}
}
}The Java version prints the array as [P5, P6, P3, P4], without quotation marks; every other line is identical.
dequeue P1
dequeue P2
['P5', 'P6', 'P3', 'P4'] front 2 rear 1 count 4
queue full
front item P3
dequeue P3
dequeue P4
dequeue P5
dequeue P6The trace table records the pointers after each operation of the program. The rear pointer starts at −1 so that the first enqueue moves it to index 0.
Operation | Returns |
|
|
| Array slots 0–3 |
|---|---|---|---|---|---|
(start) | 0 | −1 | 0 | –, –, –, – | |
| 0 | 0 | 1 | P1, –, –, – | |
| 0 | 1 | 2 | P1, P2, –, – | |
| 0 | 2 | 3 | P1, P2, P3, – | |
| P1 | 1 | 2 | 2 | –, P2, P3, – |
| P2 | 2 | 2 | 1 | –, –, P3, – |
| 2 | 3 | 2 | –, –, P3, P4 | |
| 2 | 0 | 3 | P5, –, P3, P4 | |
| 2 | 1 | 4 | P5, P6, P3, P4 | |
| queue full | 2 | 1 | 4 | unchanged |
A linear queue of the same capacity would have refused P5, because its rear pointer would already be at the last slot; the circular queue reuses slots 0 and 1, which P1 and P2 freed.
A well-built queue performs enqueue, dequeue, front and isEmpty in time: each changes one slot and one or two variables. A circular queue in an array reserves its whole capacity at creation, so its memory is fixed and it can overflow; a dynamic queue, built on a growing array or a linked structure, uses memory roughly proportional to the number of items waiting.
Problems arise when a structure's cheap end is the wrong one for the task. A Python list makes a good stack because its end is cheap to change, but its front is not: list.pop(0) must shift every remaining element one place left, so it takes time. Python's standard library provides collections.deque for queues, whose append and popleft are both . In Java, ArrayDeque is used with offer to enqueue, poll to dequeue and peek to read the front; a queue declared as Queue<String> q = new LinkedList<String>(); offers the same three methods.
A queue is the right choice whenever items must be handled in the order they arrive, often because something produces work faster than something else can process it. The queue holds the waiting items in arrival order until the slower part is ready for them.
Print queue (spooler). Documents sent to a shared printer wait in a queue and print in the order they were sent.
Keyboard buffer. Key presses are stored in a queue until the program reads them, so fast typing is not lost and the letters stay in order.
CPU scheduling. An operating system keeps processes that are ready to run in a ready queue; round robin scheduling takes the process at the front, runs it for a time slice, then enqueues it again at the rear (subtopic A1.3, Operating systems and control systems).
Network buffers. A router stores each arriving packet in a queue until the outgoing link is free, and sends packets in arrival order.
Breadth-first search. Exploring a map or a network level by level enqueues every neighbour of the current place, so nearer places are visited before farther ones.
Online waiting rooms and call centres. Customers are served in arrival order.
The waiting-room case shows that a queue's fairness depends on the system behind it. When verified fans tried to buy tickets in the presale for the US leg of Taylor Swift's Eras Tour on 15 November 2022, Ticketmaster's site could not cope with the demand, and many users reported sitting in a queue of 2,000 or more people that appeared frozen. The widespread criticism was followed by a US Department of Justice investigation into the parent company, Live Nation Entertainment.
Source: Wikipedia, "Ticketmaster" (section on the Eras Tour presale), read October 2026.
Stacks and queues are built in similar ways but release items in different orders. Both hold a sequence of items, both allow access only at their ends, both can be built on a fixed-size array (with overflow and underflow) or a dynamic structure, and both perform all four of their operations in time when built well. They differ in which item leaves next.
Stack | Queue | |
|---|---|---|
Order | LIFO: newest item leaves first | FIFO: oldest item leaves first |
Ends used | One end (the top) for adding and removing | Rear for adding, front for removing |
Pointers in an array version | One: | Two: |
Operations | push, pop, peek, isEmpty | enqueue, dequeue, front, isEmpty |
Wasted space in a simple array | None: freed slots are reused by the next push | Freed slots wasted in a linear queue; fixed by a circular queue |
Python | list: |
|
Java |
|
|
Typical uses | Undo, back button, reversing, bracket matching, backtracking, subprogram calls | Print queue, keyboard buffer, CPU scheduling, packet buffers, breadth-first search |
A combination of the two sometimes appears in one product. A text editor uses a stack for undo, because the newest change must be reversed first, but sends documents to the printer through a queue, because the oldest job must print first. In each case the choice follows from which item the problem must process next.
Data structures are examined on Paper 2. "Compare static and dynamic data structures" appears as a 4–6 mark question in which each point should refer to both structures. Construct questions ask for code that processes a 1D or 2D array or a list (totals, averages, maximums, counts, searches, adding and removing), often with built-ins such as len, sum, max or pop banned, for 4–8 marks. Trace and state questions give a sequence of stack or queue operations and ask for the contents, the pointers and the values returned, for 2–4 marks. Explain questions ask why a stack or a queue suits a scenario, or what an operation costs in time and memory, for 2–4 marks. Marks go to features of the code (correct loop bounds, correct indices, correct initialisation and update, correct return), not to perfect syntax.
When a code question bans a built-in, it usually wants the loop that the built-in hides: a counter instead of len, a running total instead of sum, a running maximum instead of max. Practise writing each of these on lists and on 2D arrays until it takes less than a minute.
Questions on performance expect precise statements: "push and pop are because only the top slot and the top pointer change" earns credit, while "stacks are fast" does not. For memory, say whether the structure reserves a fixed capacity or grows with the number of items.
Define the term dynamic data structure. [2]
State the output of the Python code nums = [4, 8, 15, 16, 23] followed by print(nums[2], nums[len(nums) - 1]) (Java: int[] nums = {4, 8, 15, 16, 23}; and System.out.println(nums[2] + " " + nums[nums.length - 1]);), and identify the index of the element 16. [2]
Outline what happens inside a dynamic array when an item is added and the array is full. [3]
Construct a function or method that takes a 2D array of integers and returns the sum of each column as a list or array, without using sum. [5]
A stack of capacity 3 is empty. Trace the operations push(1), push(2), push(3), push(4), pop(), pop(), peek(), stating what happens at each step and the final value of the top pointer (−1 when empty). [4]
Explain why a circular queue makes better use of memory than a linear queue implemented in an array. [3]
Explain why list.pop(0) is a poor way to dequeue from a long queue in Python, and state a better alternative. [3]
Construct code that removes every negative number from a list of integers, without skipping any element. [4]
A web browser stores the addresses a user has visited so that the Back button can return to each earlier site in turn. Explain which data structure is appropriate and how its operations are used. [3]
Compare a static array and a dynamic list for storing the scores of a football league that always has 20 teams, and a record of every goal scored in a season, whose number is not known in advance. [4]
A data structure whose size can change while the program runs [1]; memory is allocated or released as items are added or removed [1].
Output 15 23 [1]; 16 is at index 3 [1].
A new, larger block of memory is allocated (for example 1.5 times the old capacity) [1]; every existing element is copied into the new block [1]; the new item is stored and the old block is released [1].
Python: create totals = []; for each column c in range(cols), set total = 0, loop r over range(rows) adding grid[r][c], then totals.append(total); return totals. Java: int[] totals = new int[grid[0].length]; with an outer loop over columns and an inner loop over rows, totals[c] = totals[c] + grid[r][c];, then return totals;. Award [1] each for: outer loop over columns; total reset per column (or an array of totals); inner loop over rows; correct index grid[r][c]; returning the result.
push 1 (top 0); push 2 (top 1); push 3 (top 2, stack full); push 4 fails with stack overflow, nothing changes [1]; pop returns 3 (top 1) [1]; pop returns 2 (top 0) [1]; peek returns 1 without removing it; final top = 0 [1].
In a linear queue the front and rear pointers only move forward, so slots before the front are never reused [1]; the queue can report full while empty slots remain [1]; a circular queue wraps the pointers round with modulo, so every freed slot is reused and the full capacity is available [1].
Removing index 0 shifts every remaining element one place left [1], so each dequeue takes time proportional to the queue's length, [1]; use collections.deque with popleft(), which is (or a circular queue) [1].
Python: for i in range(len(nums) - 1, -1, -1): then if nums[i] < 0: nums.pop(i). Java: for (int i = nums.size() - 1; i >= 0; i--) { if (nums.get(i) < 0) { nums.remove(i); } }. Award [1] each for: looping from the last index down to 0; correct test for negative; removing by index; no element skipped (alternatively, building a new list of the non-negative values and returning it).
A stack [1]; each page visited is pushed onto the stack [1]; Back pops the current page and the page now on top (seen with peek) is displayed, so the most recent page is returned to first (LIFO) [1].
Both give access by index and store elements in order [1]; the 20 team scores never change in number, so a static array of length 20 allocates exactly the memory needed with no resizing [1]; the number of goals is unknown in advance, so a static array would need to be oversized (wasting memory) or could overflow [1], whereas a dynamic list grows as goals are added at the cost of spare capacity and occasional copying [1].
A data structure organises many values in memory together with the operations to use them. Static structures have a size fixed at creation; dynamic structures grow and shrink at run time.
A static array is one contiguous block, so any element's address is base address + index × element size and access by index is ; it cannot grow and may waste memory or overflow.
Java's ArrayList and Python's list are dynamic arrays: when full, they allocate a larger block (Java about 50% larger, CPython about one eighth larger), copy every element and continue, so appending is on average at the cost of spare capacity.
Arrays and lists are indexed from 0 to length − 1. Traversal uses a counted loop (index and value) or a for-each loop (value only); totals, maximums and counts can be written without built-ins.
Inserting or removing in the middle of a list shifts later elements and is ; removing during a forward traversal skips elements, so loop backwards or build a new list.
A 2D array is accessed as grid[row][col] and traversed with nested loops; in Python each row must be created separately, never with [[0] * n] * m.
A stack is LIFO: push, pop, peek and isEmpty all act on the top in time. An array-based stack uses a top pointer (−1 when empty) and can overflow (push when full) or underflow (pop when empty).
A queue is FIFO: enqueue at the rear, dequeue and front at the front, isEmpty to check. A linear array queue wastes freed slots; a circular queue wraps its pointers with (i + 1) % capacity and uses a count to tell full from empty.
Use a stack when the most recent item is needed first (undo, back button, bracket matching, backtracking) and a queue when items must be served in arrival order (printing, keyboard buffer, CPU scheduling, packet buffers).
I can explain the difference between the size and the contents of a static array, and between size and capacity in a dynamic array.
I can calculate the address of an array element from the base address, the index and the element size.
I can describe the steps of a dynamic array resize and explain why growing by a proportion is faster than growing by one slot.
I can compare static and dynamic structures point by point, with a similarity and a scenario for each.
I can create, update and traverse a 1D array in Java and a list in Python, using both a counted loop and a for-each loop.
I can find a total, average, maximum and count without sum, len, max or min.
I can use add, insert, get, set, remove and size on an ArrayList, and their equivalents on a Python list.
I can remove elements from a list during a traversal without skipping any.
I can create a 2D array in both languages, avoid the Python row-aliasing trap, and compute row and column totals with nested loops.
I can trace a sequence of push, pop and peek operations, recording the top pointer and the values returned.
I can write an array-based stack class that detects overflow and underflow.
I can trace a circular queue, including the pointer wrapping from the last index to 0.
I can explain why list.pop(0) is and name the alternatives in Python and Java.
I can justify choosing a stack or a queue for a given scenario and state what would go wrong with the other.
Award [1] for the reason and [1] for the expansion.
a queue is first in, first out / FIFO;
so jobs are printed in the order they were sent / no job is overtaken;
new jobs are added only at the rear;
so it is fair to the people who sent their jobs earlier;
the printer only ever needs the job at the front;
so dequeue gives it the next job directly without searching;
A drawing app records each action a user takes on a stack so that it can be undone. Pressing Undo pops the most recent action from the stack and reverses it. The stack starts empty.
A user carries out these actions in order:
draws a circle
fills it blue
adds a text box
presses Undo
draws a line
presses Undo
presses Undo
State the action at the top of the stack after step 3.
1 mark
State the contents of the stack after step 7, and which actions are still shown in the drawing.
2 marks
Explain why a stack, rather than a queue, is used to store the actions for undo.
3 marks
A railway depot keeps the carriages of a train in a queue on the main track. A short dead-end siding behaves like a stack: a carriage can only enter or leave the siding at one end. The depot can dequeue a carriage from the front of the main track, push it into the siding, pop the carriage nearest the siding entrance, and enqueue a carriage at the rear of the main track.
The main track currently holds five carriages:
Front | Rear | |||
|---|---|---|---|---|
C1 | C2 | C3 | C4 | C5 |
State the operation that removes C1 from the main track.
1 mark
Describe, in steps, how the depot can use the siding to reverse the order of all the carriages on the main track. Your steps must work for any number of carriages.
4 marks
State the contents of the siding when all five carriages are in it, and the order of the main track at the end of your steps in part (b).
2 marks
The depot now needs to reverse only the first three carriages and leave the rest in their original order behind them. Starting again from the queue C1, C2, C3, C4, C5, the required result is C3, C2, C1, C4, C5 (front to rear).
Describe, in steps, how this can be done using only the queue and stack operations. Your steps should work for any number of carriages n and any number k to be reversed, where k ≤ n.
5 marks
Outline how the number of operations needed in part (d) grows as the number of carriages increases.
2 marks
3
–, –, 30, 40, 50 |
enqueue 60 | 2 | 0 | 4 | 60, –, 30, 40, 50 |
enqueue 70 | 2 | 1 | 5 | 60, 70, 30, 40, 50 |
dequeue | 30 | 3 | 1 | 4 | 60, 70, –, 40, 50 |