Skip to content

Intermediate Syntax and Structures#

This page bridges Python Basics and advanced topics. It covers modern syntax (3.10+), extended unpacking, richer comprehensions, dataclass, Enum, and typing constructs you will see in production code and stronger take-home submissions.

How to use this page

Read after completing Basics. Focus on patterns you can use immediately in interview helpers and local tooling. Deep typing: Function Annotations and Type Hints.

At a glance
Track Python Intermediate
Sections 11 major topics
Outline Use the right-hand TOC to jump

Topics: Extended unpacking · Structural pattern matching (match, 3.10+) · Walrus operator (:=, 3.8+) · Advanced comprehensions · dataclass — structured records (3.7+) · Enum — named constants · Typing beyond basics · Slices and sequence tricks · … (+3 more)

  1. Extended unpacking
  2. Structural pattern matching (match, 3.10+)
  3. Walrus operator (:=, 3.8+)
  4. Advanced comprehensions
  5. dataclass — structured records (3.7+)
  6. Enum — named constants
  7. Typing beyond basics
  8. Slices and sequence tricks
  9. else on loops and try (intermediate nuance)
  10. F-strings — intermediate features
  11. Context managers preview

Extended unpacking#

Star unpacking in assignments#

first, *middle, last = [1, 2, 3, 4, 5]
# first=1, middle=[2,3,4], last=5

a, b, *rest = range(5)   # a=0, b=1, rest=[2,3,4]

# Merge iterables
combined = [*nums_a, *nums_b]
merged = {**defaults, **overrides}   # dict merge; 3.9+ also: defaults | overrides

Rules:

  • At most one starred target per assignment side.
  • Star captures a list, not a tuple (CPython 3.x).

Unpacking in function calls and definitions#

def connect(host, port, timeout=30):
    ...

config = {"host": "localhost", "port": 8080}
connect(**config)
connect(*("localhost", 8080))

def log(msg, *args, **kwargs):
    print(msg, args, kwargs)

Full parameter model: Functions and Scope, Parameter Unpacking.


Structural pattern matching (match, 3.10+)#

Beyond basic if/elif, match compares structure:

def handle(event: dict):
    match event:
        case {"type": "click", "x": x, "y": y}:
            return f"click at ({x}, {y})"
        case {"type": "key", "code": code}:
            return f"key {code}"
        case {"type": "quit"}:
            return "shutdown"
        case _:
            raise ValueError("unknown event")

Pattern types (intermediate subset)#

Pattern Example Notes
Literal case 404: Equality match
Capture case x: Binds value
Sequence case [x, y]: Fixed length
Star case [first, *rest]: Variable tail
Mapping case {"id": id, **extra}: Required keys; extras optional
OR case 401 \| 403: Multiple literals
Guard case x if x > 0: Extra condition
Class case Point(x=x, y=y): Attribute patterns
AS case [x, y] as pair: Bind whole match

Gotchas#

# Mapping patterns do NOT require exact key set — extra keys allowed
case {"a": 1}:          # matches {"a": 1, "b": 2}

# Sequence patterns require exact length unless star used
case [x, y]:            # does NOT match [1, 2, 3]

# Match uses structural rules, not duck typing inheritance

For most DSA interviews, if/elif remains default — use match when branching on shape of data (JSON events, AST nodes, command tokens).

Basics intro: Control Flow.


Walrus operator (:=, 3.8+)#

Assign and use value in one expression:

# While loop
while (line := input().strip()) != "quit":
    process(line)

# Avoid double computation in comprehension
results = [y for x in data if (y := transform(x)) > threshold]

# Conditional reuse
if (match := pattern.search(text)):
    print(match.group(1))

Do not use walrus when a plain assignment before the block is clearer — readability wins in interviews.


Advanced comprehensions#

Nested and filtered#

matrix = [[i * j for j in range(3)] for i in range(3)]

pairs = [(x, y) for x in nums if x > 0 for y in other if y != x]

# Dict / set
index = {v: i for i, v in enumerate(items)}
unique_lengths = {len(w) for w in words}

Dict comprehension vs defaultdict vs Counter#

Task Best tool
Transform keys/values once Dict comprehension
Group lists by key defaultdict(list)
Count frequencies Counter
Incremental counting in loop .get(k, 0) + 1
# WRONG for counting — O(n²)
freq = {ch: s.count(ch) for ch in set(s)}

# RIGHT
from collections import Counter
freq = Counter(s)

dataclass — structured records (3.7+)#

Reduce boilerplate for data-holding classes:

from dataclasses import dataclass, field

@dataclass
class Interval:
    start: int
    end: int

    def __post_init__(self):
        if self.start > self.end:
            raise ValueError("start must be <= end")

@dataclass(order=True)
class Task:
    priority: int
    name: str = field(compare=False)   # exclude from ordering

@dataclass(frozen=True)
class Point:
    x: int
    y: int
Option Effect
frozen=True Immutable — hashable if fields hashable
order=True Generate comparison methods
slots=True (3.10+) __slots__ — lower memory, no __dict__
field(default_factory=list) Mutable default done correctly

Compare to namedtuple and plain classes in Classes and OOP Basics.


Enum — named constants#

from enum import Enum, auto, IntEnum

class Status(Enum):
    PENDING = "pending"
    DONE = "done"
    FAILED = "failed"

class Direction(IntEnum):
    NORTH = 1
    EAST = 2
    SOUTH = 3
    WEST = 4

Status.PENDING == "pending"   # False — compare .value if needed
Status.PENDING.value == "pending"  # True

Use Enum when a fixed set of named constants improves readability (HTTP codes, states in state machines). IntEnum members compare equal to integers.


Typing beyond basics#

Type hints document intent; they are not enforced at runtime without a checker (mypy, pyright).

# Python 3.9+ — built-in generics
def merge(a: list[int], b: list[int]) -> list[int]:
    return sorted(a + b)

# Union and optional
def find(items: list[str], target: str) -> int | None:
    ...

# Callable
from collections.abc import Callable

def apply(fn: Callable[[int], int], x: int) -> int:
    return fn(x)

# Literal — fixed set of values
from typing import Literal

Mode = Literal["r", "w", "a"]
def open_mode(mode: Mode) -> None: ...

# TypedDict — dict with known keys (static checking only)
from typing import TypedDict

class UserDict(TypedDict):
    name: str
    age: int

Runtime type check: isinstance(x, list) — not type(x) == list when subclasses matter.

Deep dive: Type Hints and Annotations.


Slices and sequence tricks#

s = "abcdef"
s[::2]       # every second char
s[::-1]      # reverse
s[1:None:2]  # same as s[1::2]

lst = [0, 1, 2, 3, 4]
lst[2:2] = [99]      # insert at index 2
del lst[1:3]         # delete slice

Slice objects are reusable:

HEADER = slice(0, 10)
row[HEADER]

else on loops and try (intermediate nuance)#

Loop else#

Runs when loop completes without break:

for x in nums:
    if x == target:
        print("found")
        break
else:
    print("not found")

try/else#

Runs when try block completes without exception:

try:
    data = parse(raw)
except ParseError:
    handle_bad()
else:
    commit(data)   # only on success

Details: Error Handling.


F-strings — intermediate features#

x = 42
f"{x=}"              # "x=42" debug syntax (3.8+)
f"{x:_>10}"           # alignment and fill
f"{pi:.2f}"
f"{n/b:.1%}"          # percentage
f"{dt:%Y-%m-%d}"      # datetime format

# Expressions and calls
f"{len(items)} items"
f"{obj.method()}"

Prefer f-strings over % and .format() in new code — see Strings.


Context managers preview#

from contextlib import contextmanager

@contextmanager
def timer(label: str):
    start = time.perf_counter()
    try:
        yield
    finally:
        elapsed = time.perf_counter() - start
        print(f"{label}: {elapsed:.3f}s")

with timer("sort"):
    data.sort()

File and resource patterns: File Handling.


Interview traps (quick reference)#

Trap What goes wrong Safe approach
Star assignment twice SyntaxError One * target per side
match mapping exact keys Extra keys still match Read pattern spec
Enum vs string compare Always False Use .value or compare to Enum member
Nested comprehension unreadable Bugs in interviews Use regular loop
TypedDict at runtime No validation Treat as plain dict
Walrus in unclear spots Hard to read Assign on previous line

Mental model checklist#

  1. How does {**a, **b} differ from a \| b (3.9+)?
  2. When is match better than if/elif?
  3. Why use @dataclass(frozen=True)?
  4. What does loop else mean — when does it run?
  5. Are type hints enforced at runtime by default?

What's next#

Topic Page
Scope and closures Functions and Scope
OOP patterns Classes and OOP Basics
Stdlib data tools Data Handling and Standard Library
Advanced typing Type Hints and Annotations