Skip to content

05-06: Data Structures Comparison — str, list, tuple, set, dict

Python's five main built-in collection types serve different purposes. Understanding when to use each is a key programming skill.


At a Glance

str list tuple set dict
Ordered? Yes Yes Yes No Yes (insertion order, 3.7+)
Mutable? No Yes No Yes Yes
Indexed? Yes Yes Yes No By key
Sliceable? Yes Yes Yes No No
Duplicates? Yes Yes Yes No No (keys), Yes (values)
Hashable? Yes No Yes* No No
Use for Text Collections Fixed data Unique items Key-value pairs

*Tuple is hashable only if all its elements are hashable.


Creating Each Type

# str
s = "hello"
s2 = str(42)        # '42'

# list
lst = [1, 2, 3]
lst2 = list("abc")  # ['a', 'b', 'c']
lst3 = list(range(5))  # [0, 1, 2, 3, 4]

# tuple
t = (1, 2, 3)
t2 = tuple("abc")   # ('a', 'b', 'c')
single = (1,)       # comma required for single-element!

# set
s = {1, 2, 3}
s2 = set("hello")   # {'h', 'e', 'l', 'o'}
empty_set = set()   # NOT {} which is dict!

# dict
d = {"a": 1, "b": 2}
d2 = dict(x=10, y=20)
d3 = dict(zip(["a","b"], [1, 2]))

Mutability in Detail

Mutable — can change in-place

# list — mutable
lst = [1, 2, 3]
lst[0] = 10           # change item
lst.append(4)         # add item
lst.remove(2)         # remove item

# set — mutable
s = {1, 2, 3}
s.add(4)
s.discard(2)

# dict — mutable
d = {"a": 1}
d["b"] = 2            # add item
d["a"] = 99           # change value
del d["a"]            # remove item

Immutable — cannot change in-place

# str — immutable
s = "hello"
s[0] = "H"            # TypeError!
s = "H" + s[1:]       # must create new string

# tuple — immutable
t = (1, 2, 3)
t[0] = 99             # TypeError!
t = (99,) + t[1:]     # must create new tuple

Iteration

All five types support for loop iteration:

# str — characters
for ch in "hello":
    print(ch, end=" ")    # h e l l o

# list — items
for item in [1, 2, 3]:
    print(item, end=" ")  # 1 2 3

# tuple — items
for item in (4, 5, 6):
    print(item, end=" ")  # 4 5 6

# set — items (order not guaranteed)
for item in {7, 8, 9}:
    print(item, end=" ")  # 8 9 7  (any order)

# dict — keys by default
d = {"a": 1, "b": 2}
for key in d:
    print(key)            # a  b

for key, val in d.items():
    print(f"{key}={val}") # a=1  b=2

Sorting

lst = [3, 1, 4, 1, 5]
lst.sort()             # in-place, modifies list
s = sorted(lst)        # new sorted list

# tuple — sorted() returns a list
t = (3, 1, 4)
print(sorted(t))       # [1, 3, 4]  ← list, not tuple

# set — sorted() returns a list
st = {3, 1, 4}
print(sorted(st))      # [1, 3, 4]

# dict — sort by key
d = {"b": 2, "a": 1, "c": 3}
for k in sorted(d):
    print(k, d[k])

# sort by value
for k in sorted(d, key=lambda x: d[x]):
    print(k, d[k])

Membership Testing Speed

import timeit

data_list = list(range(10000))
data_set  = set(range(10000))
data_dict = {i: i for i in range(10000)}

# Searching for 9999 (worst case for list)
t_list = timeit.timeit("9999 in data_list", globals=globals(), number=100000)
t_set  = timeit.timeit("9999 in data_set",  globals=globals(), number=100000)
t_dict = timeit.timeit("9999 in data_dict", globals=globals(), number=100000)

print(f"list:  {t_list:.3f}s")   # ~1.0s
print(f"set:   {t_set:.3f}s")    # ~0.01s  (much faster!)
print(f"dict:  {t_dict:.3f}s")   # ~0.01s  (much faster!)

Key insight: Use set or dict for fast membership testing when collection is large.


Deduplication

original = [1, 3, 2, 1, 4, 3, 5]

# Via set — loses order
unique_unordered = list(set(original))

# Preserving order
seen = set()
unique_ordered = [x for x in original if not (x in seen or seen.add(x))]
print(unique_ordered)   # [1, 3, 2, 4, 5]

Conversion Between Types

# list ↔ tuple ↔ set
lst = [1, 2, 2, 3, 3, 4]
t   = tuple(lst)          # (1, 2, 2, 3, 3, 4)
st  = set(lst)            # {1, 2, 3, 4}
lst2 = list(st)           # some order

# str → list → str
s   = "hello"
lst = list(s)             # ['h', 'e', 'l', 'l', 'o']
s2  = "".join(lst)        # 'hello'

# dict → list of keys/values/items
d    = {"a": 1, "b": 2}
keys = list(d.keys())     # ['a', 'b']
vals = list(d.values())   # [1, 2]
items = list(d.items())   # [('a', 1), ('b', 2)]

# list of tuples → dict
pairs = [("a", 1), ("b", 2)]
d2 = dict(pairs)          # {'a': 1, 'b': 2}

Slicing Support

s  = "hello"
ls = [1, 2, 3, 4, 5]
t  = (1, 2, 3, 4, 5)

# All support slicing
print(s[1:4])    # 'ell'
print(ls[1:4])   # [2, 3, 4]
print(t[1:4])    # (2, 3, 4)

# set and dict do NOT support slicing
st = {1, 2, 3}
# st[1:3]   → TypeError!

d = {"a": 1}
# d[0:2]    → TypeError!

Deletion

# list — delete by index, slice, or value
lst = [1, 2, 3, 4, 5]
del lst[2]          # [1, 2, 4, 5]
del lst[1:3]        # [1, 5]
lst.remove(1)       # [5]
lst.pop()           # [], removed 5

# set — delete by value
s = {1, 2, 3}
s.discard(2)        # {1, 3}
s.remove(1)         # {3}

# dict — delete by key
d = {"a": 1, "b": 2}
del d["a"]          # {'b': 2}
d.pop("b")          # {}

# str, tuple — immutable, no direct deletion
# Must create a new object instead

When to Use Which

Situation Use
Text data str
Ordered, changing collection list
Ordered, fixed data (coordinates, records) tuple
Unique items, fast membership test set
Key-value mapping, fast lookup by key dict
Return multiple values from function tuple
Constant configuration data tuple
Removing duplicates set
Counting occurrences dict (or Counter)
SQL-like records list of tuple/dict

Common Patterns

# Count items — dict
text = "hello world"
counts = {}
for ch in text:
    counts[ch] = counts.get(ch, 0) + 1

# Remove duplicates from list
lst = [1, 2, 2, 3, 3]
unique = list(dict.fromkeys(lst))   # preserves order, Python 3.7+
# or
unique2 = list(set(lst))            # does NOT preserve order

# Group by property — dict of lists
data = [("Alice", "Math"), ("Bob", "CS"), ("Alice", "CS")]
groups = {}
for name, course in data:
    groups.setdefault(name, []).append(course)

# Check all unique — set
def all_unique(lst):
    return len(lst) == len(set(lst))

# Find duplicates
def find_duplicates(lst):
    seen = set()
    return {x for x in lst if x in seen or seen.add(x)}

⬅️ Previous: 05-05: Slicing — Extracting Subsequences ➡️ Next: 05-07: zip(), enumerate(), and Collection Conversions