Skip to content
VLSI Mentor

Python Part I · Module 6 · Tuples

Lists or Tuples: Which to Use

Six chapters have shown what a tuple is and what it refuses. This one answers the question that actually comes up while writing code, and it is not a question about features. Both types hold values in order; both support indexing, slicing, length and membership; one of them additionally lets you change what is inside. Phrased as a feature comparison, the list wins every time and there would be no reason for tuples to exist. Phrased correctly — as a question about what the data means — the choice is usually obvious and almost never about what you are able to do. This chapter gives you the one test worth applying, the structure most real data ends up with, and the reasons that look convincing and are not.

Foundation13 min readPythonPart IFundamentalsTuplesDesign

Part I · Module 6 · Chapter 6.7 · Lists or Tuples: Which to Use

1. The Wrong Question

Put the two side by side on capability and the comparison is lopsided:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
suite = ["smoke", "reset"]
rgb = (255, 128, 0)
print(len(suite), len(rgb))
print("smoke" in suite, 255 in rgb)
print(suite.index("reset"), rgb.index(128))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2 3
True True
1 1

Everything about reading is identical — length, membership, finding a position, and (from Chapter 6.3) indexing and slicing. The difference appears only when you try to change something:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
suite = ["smoke", "reset"]
suite[0] = "boot"
print(suite)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['boot', 'reset']
Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
rgb = (255, 128, 0)
rgb[0] = 0
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: 'tuple' object does not support item assignment

So on features, a list does everything a tuple does and more. If that were the whole story, tuples would be pointless.

It is not the whole story, because "what can I do with it" is the wrong question. The right one is "what is this data?"

2. The One Question Worth Asking

A decision figure. At the top, one question node: will it change, meaning does this collection gain, lose or reorder items. Two arrows lead down from it. The arrow labelled yes leads down-left to a node for list, whose example is a suite of test names. The arrow labelled no leads down-right to a node for tuple, whose example is a colour written as three fixed numbers.will it change?does it gain, lose orreorder items?listsuite = ["smoke", "reset"]tuplergb = (255, 128, 0)yesno12
Figure 1 — the decision, in one question. Ask whether the collection itself is expected to gain, lose or reorder items over the life of the program. A suite of tests is in motion and is a list. A colour is three fixed components whose positions have meaning, and is a tuple. Nothing about convenience or capability enters the decision.

Is this collection in motion?

A suite of tests is. It gains tests as the project grows, and a script may filter or reorder it. Chapter 5.3 exists for collections like that:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
suite = ["smoke", "reset"]
suite.append("burst")
print(suite, len(suite))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['smoke', 'reset', 'burst'] 3

A colour is not. (255, 128, 0) has three components because that is what a colour is. Position 0 is red, position 1 is green, position 2 is blue. A fourth element would not be a longer colour — it would be a different kind of thing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rgb = (255, 128, 0)
print(len(rgb))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3

Apply it to the examples from the past two modules and the answers come out immediately: the failures found so far (list — it grows), a coordinate (tuple — two numbers, always), the lines of a log file (list), a signal's name and bit width (tuple), the tests still to run (list).

3. Length Is Not the Test

A common misreading is that short collections are tuples and long ones are lists. That is not it, and the counterexamples are everywhere.

A colour has three elements and is a record. A log file's lines might number three on a quiet day and three million on a bad one, and it is a list either way — because what it is does not change with how many there happen to be.

The test is about whether the count is fixed by the data's meaning, not about what the count currently is.

4. The Shape Most Real Data Takes

In practice the question is rarely "list or tuple" for the whole structure. It is both, at different levels, and this is the single most useful shape in the module:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [("smoke", "PASS"), ("reset", "FAIL"), ("burst", "PASS")]
print(len(results))
print(results[0])
print(len(results[0]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3
('smoke', 'PASS')
2

A list of tuples. Read it with the Section 2 test applied twice:

  • The outer collection is in motion. Results arrive as tests finish, so it is a list, and it grows:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [("smoke", "PASS"), ("reset", "FAIL")]
results.append(("stress", "PASS"))
print(len(results))
print(results[-1])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3
('stress', 'PASS')
  • Each inner record is fixed. One result is a test name and an outcome — two fields, always — so it is a tuple.

And Chapter 6.5's unpacking is what makes reading it pleasant:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [("smoke", "PASS"), ("reset", "FAIL")]
first = results[0]
name, status = first
print(name, status)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke PASS

The inverse — a tuple of lists — is occasionally right and is usually a sign of confusion:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
odd = (["a"], ["b"])
print(odd, len(odd))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
(['a'], ['b']) 2

That says "a fixed pair of two things, each of which grows". It is legal and it is sometimes genuinely what you mean. More often it means somebody wanted the record to be protected and, as Chapter 6.4 showed, got no protection at all — the lists inside remain as changeable as ever, and the whole thing cannot be used as a key.

5. Reasons That Are Not Reasons

Three arguments for tuples come up constantly. None of them should decide your choice.

"Tuples are faster." There is a small difference in some operations, and in a beginner program — in almost any program — it is not what makes your script fast or slow. Choosing a data type on performance grounds without measuring is guesswork, and choosing this data type on those grounds is guessing about an effect too small to observe. If performance ever genuinely matters, measure the actual program.

"Tuples are safer." Chapter 6.4 settled this. A tuple fixes which values its positions refer to; it does not freeze those values. ("config", [1, 2]) offers no protection whatsoever to that list. Immutability is a real guarantee with a precise scope, and "safer" is not that scope.

"Tuples use less memory." Sometimes, slightly. The same answer as the first: not the basis for a design decision at this level, and not something to repeat without measuring.

6. The Type Is a Statement to the Reader

One last argument, and it is the most practical.

The same contents in the two types are not equal, and Python says so:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a = ["clk", 1]
b = ("clk", 1)
print(a == b)
print(len(a) == len(b))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
False
True

Same two values, same length, different values overall — because they are different kinds of thing.

That difference is visible to a reader at a glance, and it is information you get for free. A colleague opening your code sees ["clk", 1] and expects a collection that might grow. They see ("clk", 1) and expect a record with two meaningful positions. If you chose the type to match the data, you have told them something true before they have read a single line of the logic.

If you chose it carelessly, you have told them something false — and that is worse than telling them nothing.

7. Predict, Run, Observe

For each, decide list or tuple before reading on, and say which test you applied.

  1. the arguments to pass to a tool, built up as options are selected
  2. a screen position, x and y
  3. the names of every signal in a module, read from a file
  4. a database row: id, name, created date
  5. the failures found so far during a run

Then this, which is about the types rather than the design:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
x = ("clk", 1)
y = ["clk", 1]
print(type(x) == type(y))
print(x == y)
print(len(x) == len(y))
print(x[0] == y[0])

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
False
False
True
True

The answers to the five: list (it is built up), tuple (two fixed components), list (a collection of unknown size), tuple (a record with fixed fields), list (it grows).

And the code: the types differ, so the values are not equal — but their lengths match and their first elements are equal, because those comparisons are about the contents rather than the container. If you predicted True for the second line, you compared contents and ignored the type; == does not.

8. Exercises

Exercise 1 — apply the test. For each, choose list or tuple and give the one-sentence reason: (a) the lines of a report (b) a date as year, month, day (c) the tests that failed (d) a signal's name, width and direction (e) the seeds to sweep (f) a version number as major, minor, patch

Exercise 2 — defend the odd one. One of the items in Exercise 1 could reasonably be argued either way. Pick it, give the argument for each side, and say which you would submit for review.

Exercise 3 — both levels. Design the structure for "the result of every test in a run, where each result is a test name, an outcome and a runtime". Say which type is used at which level and why, then write it out with two results in it.

Exercise 4 — reject the reason. A colleague changes a growing collection of failures into a tuple "because tuples are faster". Write three sentences explaining what is wrong — one about the performance claim, one about what the code now says to a reader, and one about what will happen to the code that appends.

Exercise 5 — the false safety. Explain in two sentences why ("config", [1, 2]) does not protect the list inside it, referring to Chapter 6.4, and say what you would do instead if the whole record must be fixed.

Exercise 6 — predict equality. Say True or False for each and explain any that surprise you: (a) ("a", 1) == ["a", 1] (b) len(("a", 1)) == len(["a", 1]) (c) ("a", 1)[0] == ["a", 1][0] (d) type(("a",)) == type(["a"])

Exercise 7 — spot the mis-typed data. A script stores the arguments for a tool as a tuple and, every time an option is added, converts to a list, appends, and converts back. Name the symptom from Chapter 6.6, say what the data actually is, and give the change you would make.

Exercise 8 — write the rule. In two sentences and without using the words "mutable" or "immutable", state the rule you would give a new colleague for choosing between the two.

9. Summary

  • On capability the comparison is lopsided — reading is identical and only a list can be changed. That makes capability the wrong basis for the choice.
  • Ask one question: is this collection in motion? If it gains, loses or reorders items, it is a list. If its positions have fixed meaning, it is a tuple.
  • Adding an element is the test: if the result is still the same kind of thing, you have a list; if it is something else entirely, you have a record.
  • Length is not the test. A three-element colour is a record; a three-million-line log is a list.
  • "It does not change right now" is not the test either — choose on what the data means, not on what today's code happens to do.
  • Most real data is a list of tuples: a changing collection of fixed records. Apply the question once per level.
  • A tuple of lists is usually confusion — it protects nothing inside and cannot be a key.
  • Speed, memory and vague "safety" are not reasons. The real reasons are: the data is a fixed record, the shape should be visible in the code, or you need a value that can be a key.
  • The type is a statement to the reader, and == can tell the difference: ["clk", 1] == ("clk", 1) is False.

That completes Module 6 — Tuples.

You arrived knowing one ordered collection and now have two, and the second one is not a reduced version of the first. You can write a tuple and know that it is the comma rather than the parentheses that makes one; you can write a one-item tuple without falling into the trap that catches almost everyone; you can read a tuple with position rules you had already learned once; you can say precisely what immutability does and does not guarantee, including the case of a list held inside a tuple; you can take a record apart into named fields in a single line; you know the two methods a tuple has and why there are only two; and you can choose between the two collection types on the basis of what your data means rather than what you are able to do to it.

The most valuable thing here is Chapter 6.4's precision. A tuple fixes which values its positions refer to; it does not freeze those values. That sentence is doing real work — it explains the surprise of a mutable element, it explains exactly which tuples can be keys, and it is the reason "tuples are safer" is folklore rather than fact.

Module 7 — Sets introduces a third collection, and it differs on a different axis entirely. A list and a tuple are both ordered, and both allow repeats. A set does neither: it holds unique items with no order to speak of, which makes it the wrong tool for a report and the right one for the question "what is in this group that is not in that one?" — the two questions every regression review opens with. The decision framework from this chapter extends to it, and Chapter 8.9 eventually puts all of the collection types into a single choice.

Where this fits

Part of the Python curriculum.