Unit 2 · Data & Control Flow

Lesson · Unit 2 · 10 min read

Sets, tuples, and dictionaries — lists aren't the only shape data takes.

A list is the right answer maybe 60% of the time. The other 40%, you actually want a dict (key/value lookups), a set (unique values, fast membership tests), or a tuple (a fixed group that shouldn't change). Here's how to tell.

Section · 01

Dictionaries — key / value lookups

A dictionary maps a unique key to a value. Use one whenever you want to look something up by name instead of by position. Built with curly braces and colons:

prefs = {
    "theme": "dark",
    "language": "en",
    "page_size": 25,
    "notify": True,
}

prefs["theme"]              # "dark"
prefs["page_size"]          # 25

The operations you’ll use constantly:

# Read with bracket access — raises KeyError if missing
prefs["theme"]

# Read safely with .get() — returns None if missing
prefs.get("missing")              # None
prefs.get("missing", "default")   # "default"

# Add or update — same syntax
prefs["theme"] = "light"          # update existing
prefs["beta"] = True              # add new

# Remove
del prefs["beta"]

# Membership — checks KEYS, not values
"theme" in prefs                  # True
"dark"  in prefs                  # False (it's a value, not a key)

# Loop over keys, values, or both
for key in prefs:                 # keys (same as prefs.keys())
    ...
for value in prefs.values():
    ...
for key, value in prefs.items():
    ...

Keys must be unique and hashable— meaning immutable. Strings, numbers, and tuples work as keys. Lists and dicts don’t. Values can be anything.

When to reach for a dict

Any time you find yourself writing “if x == 'a' return 1, elif x == 'b' return 2…” chains, that’s a dict in disguise:

# 8-line elif chain:
def http_label(code):
    if code == 200: return "OK"
    elif code == 404: return "Not Found"
    elif code == 500: return "Server Error"
    else: return "Unknown"

# Dict — adds new codes without touching logic:
LABELS = {200: "OK", 404: "Not Found", 500: "Server Error"}

def http_label(code):
    return LABELS.get(code, "Unknown")

Section · 02

Sets — uniqueness and fast membership

A set is an unordered collection where every value is unique. Built with curly braces, no colons:

tags = {"python", "tutorial", "beginner"}

# Adding & removing
tags.add("python")            # no-op — already there
tags.add("free")              # {"python", "tutorial", "beginner", "free"}
tags.remove("tutorial")       # raises KeyError if missing
tags.discard("nope")          # safe — does nothing if missing

# Fast membership — much faster than `in` on a list for large collections
"python" in tags              # True

Two killer use cases:

1. De-duplicate a list

logins = ["ada", "ben", "ada", "cal", "ben", "ada"]
unique = list(set(logins))        # ["ada", "ben", "cal"]  (order may vary)

2. Set math

paying      = {"ada", "ben", "cal", "dee"}
active_30d  = {"ben", "cal", "ed",  "fay"}

# Customers who paid AND were active — intersection
paying & active_30d                # {"ben", "cal"}

# Customers who did either — union
paying | active_30d                # {"ada", "ben", "cal", "dee", "ed", "fay"}

# Paying but inactive — difference
paying - active_30d                # {"ada", "dee"}

# In exactly one — symmetric difference
paying ^ active_30d                # {"ada", "dee", "ed", "fay"}

Reach for a set when (a) you need to throw out duplicates, (b) you’ll do lots of inchecks, or (c) you’re doing set algebra. Order isn’t preserved, so don’t use a set when you care about sequence.

Section · 03

Tuples — fixed, ordered, immutable

A tuple is like a list, except you can’t change it after creating it. Built with parentheses (or no brackets at all):

point = (3, 7)
rgb   = (255, 99, 71)
empty = ()
single = (42,)              # the comma is required for a 1-tuple
also_a_tuple = 1, 2, 3      # parens are optional

point[0]                    # 3
point[1] = 9                # TypeError — can't change a tuple

You use tuples for three things:

1. Fixed groupings

Coordinates, RGB colors, lat/lng pairs, version numbers — any group of values where the count and order are baked in.

origin = (0, 0)
red    = (255, 0, 0)
python_version = (3, 11, 4)

2. Multiple return values

When a function returns more than one thing, it returns a tuple. You can unpack at the call site:

def parse_full(full_name):
    first, _, last = full_name.partition(" ")
    return first, last         # actually returns a tuple

first, last = parse_full("Ada Lovelace")

3. Dictionary keys

A list can’t be a dict key (lists are mutable). A tuple can:

# A cache keyed by (city, day)
forecast = {}
forecast[("NYC", "2025-01-15")] = "snow"
forecast[("LA",  "2025-01-15")] = "sun"

Section · 04

Picking the right collection

A quick decision tree:

Need to look things up by name?         → dict
Need to throw out duplicates?           → set
Need to check membership a lot?         → set
Need a fixed, won't-change group?       → tuple
Need an ordered, growing collection?    → list
None of the above?                       → list (it's the default)

Beginners default to lists for everything, which works but reads worse and runs slower for the wrong job. Two months in, you’ll start noticing “wait, this should be a dict” or “this should be a set,” and your code gets sharper.

All four types compared

            | ordered | mutable | duplicates | syntax
------------+---------+---------+------------+------------
list        |   yes   |   yes   |    yes     | [1, 2, 3]
tuple       |   yes   |   NO    |    yes     | (1, 2, 3)
set         |   no    |   yes   |    NO      | {1, 2, 3}
dict        |   yes*  |   yes   |  keys NO   | {"a": 1}

* Dicts preserve insertion order in Python 3.7+, but you shouldn't rely on
  it for anything where order is meaningful. Use a list if order matters.

Curriculum source

Lesson content is original to YorkSims. Topic structure aligns with Python for Everybody by Dr. Charles R. Severance (py4e.com), licensed under Creative Commons Attribution 3.0 Unported.