Studio Matrx Monthly · Volume 1 · Issue 3 · August 2026
Amogh N P
 In loving memory of Amogh N P — Architect · Designer · Visionary 
Lists & TuplesLesson 2.3
PSD for Architecture, Planning & Urban Design/Module 2 · Control Flow & Collections

Lesson 2.3 · Control Flow & Collections

Lists & Tuples

The workhorse collection: how to hold many things in order, reach into them, grow them, and know when to freeze them

13 min Interactive lessonFree · open lessonByAmogh N P· Architect & interior designer
The hook

A single variable holds one thing. A list holds a whole schedule - and lets a loop run down it.

So far our variables have each held a single value - one area, one name. But design is made of collections: a set of rooms, a run of areas, a series of points, a folder of files. The list is Python's everyday container for exactly this - an ordered sequence of items you can index into, grow, sort and loop over.

If the loop from the last lesson is the engine of leverage, the list is the fuel it runs on. Get comfortable creating lists, reaching into them by position, slicing out ranges and appending new items, and you have the single most useful data structure in the language. We will also meet the tuple - a list that cannot change - and end with the list comprehension, a compact one-liner that builds a new list from an old one.

List = ordered boxes, index from 0. Slice stops before the stop. Tuple = frozen list. Comprehension = loop in a line.

Making a list and reaching into it by index

You write a list with square brackets and commas, holding items of any type - usually all the same type, because they mean the same kind of thing:

python
rooms = ["living", "kitchen", "bath", "bed1", "bed2"]
areas = [24.0, 12.5, 4.5, 15.0, 12.0]

Each item sits at a numbered position, called its index, and here is the rule that catches everyone at first: indexing starts at 0. So rooms[0] is "living", rooms[1] is "kitchen", and rooms[4] is the fifth and last item, "bed2". Asking for rooms[5] raises an IndexError because there is no sixth item. Python also lets you count from the end with negative indices: rooms[-1] is the last item, rooms[-2] the second-to-last - handy when you do not know or care how long the list is.

python
print(rooms[0])    # living
print(rooms[-1])   # bed2
print(len(rooms))  # 5  - how many items

The built-in len() tells you how many items a list holds, which you will use constantly. Zero-based indexing feels arbitrary until you realise it pairs perfectly with the range() and loop behaviour from the last lesson - the valid indices of a list of length 5 are exactly 0, 1, 2, 3, 4, which is precisely what range(len(rooms)) gives you.

One mental model helps all of this stick: picture a list as a row of numbered pigeon-holes. The number on each hole is its index, counting from zero at the left; the thing inside is the item. Indexing hands you what is in one hole; slicing hands you a fresh row copied from a range of holes; len tells you how many holes there are. Every list operation is some variation on reaching into, copying from, or adding holes to that row - and once the picture is fixed, the off-by-one worries fade. It is a picture worth keeping, because the very same indexing carries straight over to strings, to rows of tabular data, and later to arrays of points and pixels.

A LIST IS BOXES IN ORDER, EACH WITH AN INDEX01234livingkitchenbathbed1bed2-5-4-3-2-1rooms =rooms[0] -> "living"rooms[-1] -> "bed2" (last)rooms[1:3] -> ["kitchen", "bath"]len(rooms) -> 5
Zoom
A list is a row of boxes in order, each addressed by an index. Positions count from 0 at the front and from -1 at the back, so rooms[0] is the first item and rooms[-1] is the last. A slice like rooms[1:3] pulls out a range - starting at 1, stopping before 3 - and returns a new list of those items.

Slicing: pulling out a range of items

Where an index gives you one item, a slice gives you a range of them, using the colon: list[start:stop]. As with range(), the slice includes the start position but stops before the stop position - the same half-open rule, which is why it looks off by one until it becomes second nature:

python
rooms = ["living", "kitchen", "bath", "bed1", "bed2"]
print(rooms[1:3])   # ['kitchen', 'bath']  - positions 1 and 2
print(rooms[:2])    # ['living', 'kitchen'] - from the start
print(rooms[3:])    # ['bed1', 'bed2']      - to the end
print(rooms[-2:])   # ['bed1', 'bed2']      - last two

Leaving out the start means 'from the beginning'; leaving out the stop means 'to the end'. A slice always hands back a new list, leaving the original untouched - so first_two = rooms[:2] gives you a fresh two-item list you can work with independently. This same slicing syntax works on strings too (a string is a sequence of characters), which is why name[:3] gives the first three letters. Slicing is how you grab the first N drawings, the last few rooms, or every item except the header row of some data - a small piece of syntax you will reach for far more than you expect.

A LIST IS BOXES IN ORDER, EACH WITH AN INDEX01234livingkitchenbathbed1bed2-5-4-3-2-1rooms =rooms[0] -> "living"rooms[-1] -> "bed2" (last)rooms[1:3] -> ["kitchen", "bath"]len(rooms) -> 5
Zoom
A list is a row of boxes in order, each addressed by an index. Positions count from 0 at the front and from -1 at the back, so rooms[0] is the first item and rooms[-1] is the last. A slice like rooms[1:3] pulls out a range - starting at 1, stopping before 3 - and returns a new list of those items.

Growing and changing a list - and its methods

Lists are mutable - they can change after you make them, which is exactly what you want when you are building up a result inside a loop. The most common move is .append(), which adds one item to the end:

python
large_rooms = []                 # start empty
for room, area in zip(rooms, areas):
    if area >= 15:
        large_rooms.append(room)
print(large_rooms)   # ['living', 'bed1']

That pattern - start an empty list, loop, and .append() the items that pass a test - is one you will write hundreds of times; it is how you filter and collect. Beyond append, a list carries a toolkit of methods, functions attached to it that you call with a dot: .sort() orders it in place, .reverse() flips it, .insert(i, x) puts an item at a position, .remove(x) deletes the first matching item, .pop() removes and returns the last, and .count(x) tallies occurrences. You can also change an item directly by its index - rooms[0] = "lounge" - and join two lists with +. A word of care that follows from mutability: writing b = a for two lists does not copy the list, it gives the same list a second name, so changing b also changes a. When you truly want an independent copy, use a.copy() or a[:]. This shared-name behaviour surprises nearly every beginner once, and knowing it exists saves a baffling afternoon.

Empty list + loop + .append() = filter-and-collect. b = a does NOT copy; use a.copy().

Tuples, and a first list comprehension

A tuple is a list that cannot change. You write it with parentheses instead of square brackets, and once made, you cannot append to it or reassign its items - it is immutable:

python
point = (12.0, 9.0)     # an (x, y) coordinate
print(point[0])         # 12.0  - index it just like a list
# point[0] = 5.0        # ERROR - tuples cannot change

Why would you want a collection you cannot edit? Because immutability is a feature when the grouping is fixed: an (x, y) or (x, y, z) point, an (R, G, B) colour, a (width, height) pair. The tuple signals 'these belong together and should not be altered', and it can be used as a dictionary key (next lesson) where a list cannot. Use a list when the collection grows or changes; use a tuple for a fixed little record.

Finally, a taste of one of Python's most-loved features, the list comprehension - a compact way to build a new list from an existing one, folding a loop into a single readable line:

python
areas = [24.0, 12.5, 4.5, 15.0]
sqft = [a * 10.764 for a in areas]              # convert every area
big = [a for a in areas if a >= 12]             # keep only the big ones
print(sqft)   # [258.3, 134.5, 48.4, 161.5]
print(big)    # [24.0, 12.5, 15.0]

Read [a * 10.764 for a in areas] as 'a times 10.764, for each a in areas'. It does exactly what an empty-list-plus-append loop does, in one line, and the optional if filters as it goes. Do not force everything into a comprehension - a plain loop is clearer when the body is long - but for the very common 'transform or filter a list into a new list', it is the idiomatic Python move, and you will see it everywhere in the modules ahead.

LIST vs TUPLE: CAN IT CHANGE?list [ ] - mutable3.04.52.1.append(5.0) oksizes[0] = 3.5 oktuple ( ) - frozen12.09.0point[0] = 5 ERRORuse it for fixed pairsUse a list when the collection grows or changes; a tuple for a fixed record like an (x, y) point.
Zoom
List versus tuple: can it change? A list uses square brackets and is mutable - you can append to it and reassign its items. A tuple uses parentheses and is frozen - trying to change an item raises an error. Reach for a list when the collection grows or changes, and a tuple for a fixed record like an (x, y) point.

Nesting, unpacking, and the list habits worth keeping

Two more moves make lists genuinely comfortable. The first is the list of lists - a list whose items are themselves lists, which is how a table lives in plain Python before you reach a library like pandas. Each inner list is a row:

python
schedule = [
    ["living", 24.0],
    ["kitchen", 12.5],
    ["bath", 4.5],
]
print(schedule[0])      # ['living', 24.0]  - the first row
print(schedule[0][1])   # 24.0             - row 0, column 1

You reach a cell with two indices - schedule[0][1] means 'first row, second column'. This nesting is exactly the shape of a spreadsheet, and looping over it row by row is how you would total a column or reformat a schedule by hand before a library does it for you.

The second move is unpacking, which makes looping over such rows a pleasure. Rather than reaching in by index, you name the parts directly in the for line:

python
for name, area in schedule:
    print(f"{name}: {area} sqm")

Each inner list (or tuple) is unpacked into name and area in one step - cleaner than row[0] and row[1], and it reads like the data it describes. This is the same unpacking that let enumerate and zip hand you two variables at once in the last lesson, and the same idea behind x, y = point for a coordinate pair.

Finally, a handful of built-in functions turn a list into an answer without a loop at all: sum(areas) totals it, min(areas) and max(areas) find the extremes, len(areas) counts it, and sorted(areas) returns a new sorted list - leaving the original untouched, unlike .sort(), which reorders in place and returns None. The in keyword tests membership (if "kitchen" in rooms:) and .index(x) finds an item's position. Knowing these saves you from re-writing loops for jobs Python already does: when you catch yourself looping just to add numbers up or find the largest, reach for the built-in instead. Between indexing, slicing, appending, nesting, unpacking and this toolkit of built-ins, the list will carry more of your scripting than any other structure - which is exactly why it repays learning its habits properly now, on data you understand, before the later modules pile real complexity on top.

It is also worth a word on when not to reach for a list. A list is the right home for an ordered sequence you process or index, but if you find yourself constantly searching a list for a particular item by name - scanning every entry to find 'the kitchen' - that searching is a sign the data really wants a dictionary, which the next lesson introduces. And if you are only ever asking 'is this value present?' or 'what are the distinct values?', a set will serve you better. The list is the default and the workhorse, but part of using it well is recognising the two moments its cousins do the job more cleanly: lookup by name, and questions of uniqueness.

Concepts & methods in this lesson

list [ ]

An ordered, mutable sequence of items

The workhorse collection. Index from 0, slice with a colon, grow with .append(). Your loops mostly run over these.

Indexing & slicing

list[i] for one item, list[start:stop] for a range

Zero-based; negative indices count from the end; slices stop BEFORE the stop and return a new list.

.append() and list methods

Mutating methods: append, sort, insert, remove, pop, count

Called with a dot. .sort() and .append() change the list in place and return None - do not assign their result.

tuple ( )

An ordered but immutable sequence

Cannot change after creation. Use for fixed records like (x, y) points; can be a dict key where a list cannot.

List comprehension

[expr for item in seq if cond] - build a new list in one line

A loop folded into an expression, with an optional filter. Idiomatic for transform/filter; avoid when the body gets long.

Hands-on workshop

Workshop - a room-area toolkit

Build a small toolkit around two parallel lists - room names and their areas - practising indexing, slicing, appending, methods and a first comprehension. This is the raw material of an area schedule.

Python 3 in any editor or notebook. No libraries needed.

Given & goal
Goal: index, slice, grow and transform lists of rooms and areas
Inputs: `rooms = ["living", "kitchen", "bath", "bed1", "bed2"]` and `areas = [24.0, 12.5, 4.5, 15.0, 12.0]`
Time: ~30 minutes
  1. 1Print the first room, the last room (using a negative index), and the number of rooms with len().
  2. 2Slice out and print the two bedrooms at the end using rooms[-2:], then the first three rooms with rooms[:3].
  3. 3A new room was added: .append("study") to rooms and .append(9.0) to areas, then print both lists to confirm they stayed the same length.
  4. 4Build a list large_rooms of the names whose area is 15 or more, using an empty list, a for loop over zip(rooms, areas), an if, and .append(). Print it.
  5. 5Now build the SAME large_rooms list in a single line with a list comprehension and confirm it matches.
  6. 6Sort a copy of the areas descending and print it, being careful to copy first (areas.copy()) so the original order is preserved. Note what areas.sort() returns if you accidentally assign it.

You’ll walk away with
A script that prints selected items and slices, appends a new room while keeping the two lists aligned, produces a filtered list of large rooms both with a loop and with a comprehension (matching), and sorts a copy without disturbing the original - plus a one-line note on why copying mattered.

The worked example

Three altitudes on the same idea

Read the band that fits you — or all three.

For the architectAutomate busywork & build custom tools

Lists are how a schedule lives inside a script. A run of room names, a column of areas, a set of sheet numbers, a series of grid coordinates - each is a list you index, slice and loop. Filtering the rooms over an area limit, collecting every sheet that needs re-issue, or building an (x, y) grid of column positions as tuples are all list work. When you reach Rhino and Grasshopper, a list of points is the raw material of generated geometry, and the indexing and slicing you learn here is exactly how you address it.

For the interior designerScripts for data, schedules & layouts

Your schedules and matrices are lists waiting to be scripted. A finishes list, a run of product costs, a batch of image filenames - hold them in a list and you can sum, sort, filter and relabel them with a loop or a one-line comprehension. Want just the items over a price threshold, or the first ten products for a moodboard? That is a slice or a filtered comprehension. Pairing a room list with an area list (or better, moving to a dictionary next lesson) is how a spec sheet becomes something a script maintains for you.

For the studentA hireable computational skill

The list is the data structure you will use more than any other, so learn its habits deeply. Zero-based indexing, half-open slices, mutability and the copy-versus-alias trap are exactly the details that separate code that works from code that mysteriously does not. The list comprehension you meet here is a signature of fluent Python and a favourite in coursework and interviews. Every later topic - pandas tables, geometry point-lists, generative arrays - assumes this fluency, so time spent here compounds.

Misconception check

Copying a list is just `new_list = old_list` - now I have two independent lists.

This is the single most surprising beginner trap with lists, and it comes straight from mutability. Writing new_list = old_list does not copy anything - it gives the very same list a second name. Both names point at one object, so new_list.append("x") also changes old_list, which can produce baffling bugs where data you never meant to touch mysteriously changes. When you want a genuinely separate list, make an explicit copy with old_list.copy() or the slice old_list[:]. The same caution applies inside loops and functions - if you pass a list around and modify it, you are modifying the original everywhere it is named. Numbers and strings do not behave this way because they are immutable; lists do because they can change in place, and understanding that distinction saves real time.
Try it

Do it yourself

Predict each result before you run it.

  1. 1What does rooms[0] give, and why is it not the second item?
  2. 2What does the slice rooms[1:3] return, and how many items is that?
  3. 3Why does new = old not give you an independent copy of a list, and what does?
  4. 4When would you choose a tuple instead of a list?
  5. 5Rewrite result = [] plus a for loop that appends a * 2 for each a in areas as a single list comprehension.
Take this with you

The one line to carry out

A list holds many items in order - index from 0, slice with a colon, grow with `.append()`, loop to process - while a tuple is its frozen form for fixed records, and a list comprehension builds a new list in one readable line. It is the collection your scripts will lean on most.
Take it further
References & further reading

Peer-reviewed journals & authoritative standards

  1. 01List (abstract data type)Wikipedia, 2026.
  2. 02TupleWikipedia, 2026.
  3. 03The Python TutorialPython Software Foundation, 2026.
  4. 04Data typeWikipedia, 2026.
Related lessons
Recap
A list is an ordered, changeable sequence written with square brackets. You reach one item by its index (starting at 0, or from the end with negatives), a range of items by slicing (which stops before the stop and returns a new list), and you grow it with .append() and reshape it with methods like .sort(). Assigning a list to a new name shares it rather than copying it. A tuple is an immutable list for fixed groupings like points, and a list comprehension folds a filter-or-transform loop into a single expression.
Carry forward →

A list is perfect when order matters and you look things up by position. But often you want to look something up by _name_ - the area of 'the kitchen', not the item at position 1. That is a dictionary, and with sets for uniqueness, it completes your core collections.

A

The author

Amogh N P

Architect, interior designer, and creative polymath. Studio Matrx began in his notebooks — his vision of design made honest, useful, and open to everyone. Its Academy is written and taught in his memory, and free, forever.

More about Amogh →