Skip to content
6 min read·Lesson 2 of 10

Data Types and Control Flow

The Python data types and control structures you will reach for daily — strings, lists, dicts, sets, loops, conditionals, comprehensions, and functions.

You don't need to learn every corner of Python before being productive. This lesson covers the data types and control structures you'll touch every day in DevOps work.

Primitive Types

name = "alex"          # str
count = 42             # int
ratio = 3.14           # float
active = True          # bool
nothing = None         # NoneType
type(count)            # <class 'int'>

Python is dynamically typed — variables don't declare a type, but they always have one. You can add optional type hints:

def greet(name: str) -> str:
    return f"hello, {name}"

Type hints aren't enforced at runtime; they're for tools like mypy/pyright and your editor. Use them in anything beyond a throwaway script.

Strings and f-strings

user = "alex"
count = 7
print(f"user {user} has {count} items")
print(f"upper: {user.upper()}, len: {len(user)}")
print(f"{count:04d}")     # 0007 — zero-padded
print(f"{3.14159:.2f}")   # 3.14 — two decimals

F-strings (Python 3.6+) are the modern way to format. Avoid the older % and .format() styles in new code.

Collections

TypeSyntaxProperties
list[1, 2, 3]ordered, mutable, allows duplicates
tuple(1, 2, 3)ordered, immutable, allows duplicates
set{1, 2, 3}unordered, mutable, no duplicates
dict{"a": 1}key-value, ordered (insertion), mutable
servers = ["web-1", "web-2", "db-1"]
servers.append("cache-1")
servers[0]            # 'web-1'
servers[-1]           # 'cache-1'
servers[1:3]          # ['web-2', 'db-1']

config = {"region": "us-east-1", "instance_type": "t3.micro"}
config["region"]
config.get("missing", "default")
for key, value in config.items():
    print(key, value)

unique_ips = set(["1.1.1.1", "1.1.1.1", "8.8.8.8"])  # 2 elements

Conditionals

status_code = 503

if status_code < 300:
    print("ok")
elif status_code < 400:
    print("redirect")
elif status_code < 500:
    print("client error")
else:
    print("server error")

Truthiness: empty collections, 0, empty string, and None are all falsy. So if items: is the idiomatic way to test "do I have any items".

Loops

for server in servers:
    print(server)

for i, server in enumerate(servers):
    print(f"{i}: {server}")

for region, az_count in [("us-east-1", 3), ("eu-west-1", 2)]:
    print(region, az_count)

count = 5
while count > 0:
    print(count)
    count -= 1

break exits a loop; continue skips to the next iteration; else on a loop runs only if the loop didn't break.

Comprehensions

The Pythonic way to build a new collection from an existing one:

numbers = [1, 2, 3, 4, 5]
squares = [n * n for n in numbers]
evens = [n for n in numbers if n % 2 == 0]

names = ["Alex", "Sam", "Jamie"]
upper_map = {name: name.upper() for name in names}

unique_first_letters = {name[0] for name in names}

Use comprehensions for clarity, not contortion. If yours needs more than one for or a complex filter, write a normal loop.

Functions

def fetch(url: str, timeout: int = 30, *, retries: int = 3) -> dict:
    """Fetch a URL with retries. Keyword-only retries."""
    ...

# Positional + keyword
fetch("https://example.com", 10, retries=5)
# All keyword
fetch(url="https://example.com", timeout=10)

Notes:

  • Arguments after a bare * are keyword-only — callers must name them. Improves readability of boolean flags.
  • Default values are evaluated once at definition time. Never use mutable defaults like def foo(items=[]); use None and assign inside the body.
  • *args collects extra positionals into a tuple; **kwargs collects extra keyword args into a dict.

Pattern Matching (3.10+)

Cleaner branching on the structure of data:

def describe(event: dict) -> str:
    match event:
        case {"type": "create", "resource": name}:
            return f"creating {name}"
        case {"type": "delete", "resource": name}:
            return f"deleting {name}"
        case {"type": kind}:
            return f"unknown event: {kind}"
        case _:
            return "malformed event"

Truthy Patterns You'll See Constantly

# Default if missing or falsy
region = config.get("region") or "us-east-1"

# Walrus operator (3.8+) — assign and test in one go
if (n := len(items)) > 100:
    print(f"large batch of {n}")

# Unpacking
first, *rest = [1, 2, 3, 4]
a, b = b, a   # swap

You don't need to memorise every detail of these. Build mental shortcuts as you encounter them in real code.

Key Takeaways

  • Strings, ints, floats, bools, None — the basic building blocks; learn f-strings.
  • Lists are ordered, mutable; tuples are immutable; sets are unique; dicts map keys to values.
  • List/dict comprehensions are the Pythonic alternative to building collections in loops.
  • Functions take args, kwargs, and return values; default arguments must be immutable.
  • Pattern matching (match/case) makes branching on shape clean.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →