Your language · Python
Collections: lists, sets and maps
The built-in collections, when to use each, and how they behave.
Learn: Python collections: lists, tuples, dicts and sets9 minute read
A collection holds a group of values. Python has four built-in ones, and they cover nearly every need in test code: a list of elements found on a page, tuples of test parameters, a dict for a JSON body (structured data sent to a web service) or headers (extra information sent with a web request), and a set for uniqueness checks. Interviewers ask when to use each one, how a dict finds a key, and about a few classic traps with copies and loops.
Step 1: the four built-ins
| Type | Ordered | Can change | Duplicates | Typical use |
|---|---|---|---|---|
list [1, 2] | Yes | Yes | Yes | Elements on a page, column values |
tuple (1, 2) | Yes | No | Yes | Fixed records, parametrize rows, locators |
dict {"k": 1} | Insertion order | Yes | Keys unique | JSON bodies, headers, counts |
set {1, 2} | No | Yes | No | Uniqueness, comparing without order |
Two syntax traps: {} is an empty dict (an empty set is set()), and (1) is just the number 1 (a one-item tuple needs a comma: (1,)). Since Python 3.7, dicts are guaranteed to keep insertion order (the order in which you added the keys). In the table, "can change" means you can add, remove or replace items after creating it.
Step 2: how a dict finds a key
A dict is a hash table. To store a key, Python calls hash(key) (a function that turns the key into a number) to choose a slot (a numbered place) in an internal table. If the slot is taken, it tries other slots, and it matches keys by hash and then by ==. Assigning to an existing key replaces the value and keeps the key's position. On average, a lookup takes the same short time however big the dict is (written O(1)), so key in some_dict and x in some_set are much faster than x in some_list on large data.
Keys must be hashable, which means their hash can never change. Strings, numbers and tuples work; lists and dicts do not. A set is the same structure with keys only.
links = ["Home", "Login", "Home", "Cart", "Login"]
print(len(set(links)), sorted(set(links)))
print(list(dict.fromkeys(links))) # unique, page order kept
counts = {}
for link in links:
counts[link] = counts.get(link, 0) + 1
print(counts)
print(counts.get("Help"), counts.get("Help", 0))
try:
counts["Help"]
except KeyError as e:
print("KeyError:", e)
by_count = dict(sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])))
print(by_count)
expected_options = {"India", "Japan", "Peru"}
actual_options = {"Peru", "India", "Chile"}
print("missing:", expected_options - actual_options, "extra:", actual_options - expected_options)
try:
{["a"]: 1}
except TypeError as e:
print("TypeError:", e)
3 ['Cart', 'Home', 'Login']
['Home', 'Login', 'Cart']
{'Home': 2, 'Login': 2, 'Cart': 1}
None 0
KeyError: 'Help'
{'Home': 2, 'Login': 2, 'Cart': 1}
missing: {'Japan'} extra: {'Chile'}
TypeError: unhashable type: 'list'
Set subtraction is a quick way to report which dropdown options are missing or extra.
Step 3: lists, sorting and safe loops
A list is a dynamic array of references (a row of memory that grows as needed and points at the objects). Reading by index and adding at the end are fast; inserting or removing at the front is slow because every item shifts, so use collections.deque for a queue. lst.sort() sorts in place and returns None, while sorted(lst) returns a new list. Both take a key function (it tells sort what to compare) instead of a comparator: sorted(users, key=lambda u: (u["role"], u["name"])). A lambda is a short function written in one line; see Lambdas and comprehensions. To check that a column on a page is sorted, compare it with sorted(values).
Never remove items from a list while you loop over it: the loop silently skips the item after each removal. Build a new list with a comprehension instead (a one-line loop that builds a list, such as [n for n in nums if n % 2]). Changing a dict's size while looping over it raises RuntimeError (a general error while the program runs).
Step 4: copies and the collections module
Assignment never copies: b = a gives one list two names. a.copy(), a[:] and list(a) make a shallow copy, which is a new outer list that still shares the inner objects. copy.deepcopy copies every level, which you need before changing nested data, such as a JSON template (a ready-made example of a request body).
import copy
from collections import Counter, defaultdict, deque
template = {"user": {"name": "Asha", "roles": ["viewer"]}}
shallow = copy.copy(template)
deep = copy.deepcopy(template)
template["user"]["roles"].append("admin")
print(shallow["user"]["roles"], deep["user"]["roles"])
grid = [[0] * 2] * 2
grid[0][0] = 1
print(grid)
grid = [[0] * 2 for _ in range(2)]
grid[0][0] = 1
print(grid)
nums = [1, 2, 2, 3, 4]
nums = [n for n in nums if n % 2]
print(nums)
print(Counter("banana").most_common(2))
by_team = defaultdict(list)
for name, team in [("Asha", "QA"), ("Ravi", "Dev"), ("Bea", "QA")]:
by_team[team].append(name)
print(dict(by_team))
recent = deque(maxlen=3)
for line in ["l1", "l2", "l3", "l4"]:
recent.append(line)
print(list(recent))
['viewer', 'admin'] ['viewer']
[[1, 0], [1, 0]]
[[1, 0], [0, 0]]
[1, 3]
[('a', 3), ('n', 2)]
{'QA': ['Asha', 'Bea'], 'Dev': ['Ravi']}
['l2', 'l3', 'l4']
[[0] * 2] * 2 repeats one inner list, so changing one row changed both. The collections module adds Counter for counting, defaultdict for grouping, deque for fast queues (with maxlen to keep the last N items) and namedtuple for small records.
How to answer in an interview
Compare the types on the same few points: order, whether they can change, duplicates, and lookup speed. Then give a test use for each: "find_elements returns a list; I pass (user, password) tuples to parametrize (a pytest feature that runs one test with several rows of data); the response body is a dict; I use a set to prove IDs are unique." For "how does a dict work?", walk through hashing the key, choosing a slot and matching with ==.
Common mistakes
- Writing
{}for an empty set, or(1)for a one-item tuple. - Writing
x = lst.sort()and gettingNone. - Removing items from a list inside a loop over the same list.
- Changing a "copy" made with
b = a, or a shallow copy of nested data. - Using
d["key"]when the key may be missing;d.get("key")returnsNoneinstead of raisingKeyError(the error Python gives for a missing key).
Recap
Use a list for ordered data, a tuple for fixed records, a dict for key lookups and a set for uniqueness. Dicts and sets are hash tables, so keys must be hashable and lookups are fast. Sort with a key function, filter into a new list instead of removing in a loop, and use deepcopy before changing nested data.
11 questions0/11 solved
| Status | Question | Difficulty | Asked in |
|---|---|---|---|
| Not started | Remove duplicates but keep the order | Easy | Asked in: Accenture, Capgemini, Cognizant +3 |
| Not started | list vs tuple vs dict vs set | Easy | Asked in: Accenture, Capgemini, Infosys +2 |
| Pro+ | How does a dict work internally? | Medium | Asked in: Accenture, Capgemini, Cognizant +1 |
| Pro+ | Lists, arrays and deques | Medium | Asked in: Accenture, Capgemini, Infosys +2 |
| Pro+ | What can be a dict key? | Easy-Medium | |
| Pro+ | Changing a collection while looping over it | Medium | Asked in: Accenture, Infosys, TCS |
| Pro+ | Ways to loop over a dict | Easy-Medium | Asked in: Accenture, Infosys, TCS |
| Pro+ | Sorting with key functions; sorting a dict by value | Medium | Asked in: Accenture, Infosys |
| Pro+ | Aliases, shallow copies and the [[0] * 2] * 2 trap | Medium | |
| Pro+ | Counter, defaultdict, deque and namedtuple | Easy-Medium | |
| Pro+ | sort() versus sorted() | Easy-Medium | Asked in: Accenture, Infosys |
The first 2 questions of each topic are free. Pro+ opens every question.