# Python Basics: Names, Collections, Control Flow and Comprehensions

> Python basics with runnable examples: names and objects, lists, dicts and sets, control flow and comprehensions, plus the traps behind most beginner bugs.

- **Author:** Ayushi Kulshreshta — AI Engineer (https://www.1stepgrow.com/authors/ayushi-kulshreshta/)
- **Published:** Jul 13, 2026 · **Updated:** Sep 17, 2026
- **Topic:** Python & Programming · **Format:** Guide · **Read time:** 10 min
- **Canonical URL:** https://www.1stepgrow.com/articles/python-basics/

## Key takeaways

- Variables are names bound to objects, not boxes holding values — this explains most surprising aliasing behaviour.
- Choose the data structure by access pattern: list for order, dict for lookup, set for membership and uniqueness.
- Set and dict membership is O(1) on average; list membership is O(n). Converting once often removes a bottleneck.
- Comprehensions are the idiomatic way to build collections, but a loop is better once logic gets complex.
- Most beginner bugs come from a handful of traps: shared inner lists, {} creating a dict, and methods like sort() that return None.

Python basics come down to a small set of pieces: names bound to objects, four core collections (list, tuple, dict and set), a few control-flow statements, and comprehensions. Get these right early, and the rest of the language becomes considerably less surprising.

Most beginner bugs come from misreading those pieces rather than from anything advanced. `[[0] * 3] * 3` builds three references to one list, `{}` makes a dict rather than a set, and `nums = nums.sort()` throws your list away. This guide is for new Python programmers, including those heading into data work. It explains the model behind each piece so those bugs make sense, with runnable examples and a quick reference for choosing a collection.

This guide is the first stop in a short series. Once you are comfortable here, the natural follow-ups are [Python numeric data types](https://www.1stepgrow.com/articles/python-numeric-data-types), [strings](https://www.1stepgrow.com/articles/python-strings), [operators](https://www.1stepgrow.com/articles/python-operators) and [user-defined functions](https://www.1stepgrow.com/articles/python-functions).

## How do Python variables actually work?

A variable is a name bound to an object, not a container holding a value. That one mental model explains most of the confusing behaviour beginners meet.

```python
a = [1, 2, 3]
b = a              # b is another name for the SAME list
b.append(4)
print(a)           # [1, 2, 3, 4]
```

Nothing was copied. Instead, there are two names and one object.

For immutable objects, however, this never causes trouble, because you cannot modify them in place:

```python
x = 5
y = x
y += 1       # rebinds y to a new int
print(x)     # 5
```

When you want independence, you therefore have to ask for a copy:

```python
a = [[1, 2], [3, 4]]
b = a.copy()          # shallow: new outer list, same inner lists
b = a[:]              # same thing

import copy
b = copy.deepcopy(a)  # recursive, for nested mutable structures
```

Shallow copies are the usual gotcha, because they share anything nested:

```python
grid = [[0, 0], [0, 0]]
shallow = grid.copy()
shallow[0][0] = 9
print(grid)      # [[9, 0], [0, 0]]  - inner lists are shared
```

The classic multiplication trap follows from the same rule:

```python
rows = [[0] * 3] * 3      # BROKEN - three references to ONE list
rows[0][0] = 1
print(rows)               # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

rows = [[0] * 3 for _ in range(3)]    # correct - three distinct lists
```

The [`copy` module documentation](https://docs.python.org/3/library/copy.html) spells out exactly what shallow and deep copies duplicate.

## Which Python data structures should you learn first?

Learn the four built-in collections first, because they sit at the heart of Python basics and nearly every program uses them. The official [data structures tutorial](https://docs.python.org/3/tutorial/datastructures.html) covers each in more depth.

**List** — ordered, mutable, allows duplicates:

```python
nums = [3, 1, 4, 1, 5]
nums.append(9)
nums.insert(0, 0)
nums.remove(1)        # removes the FIRST 1
nums.pop()            # removes and returns the last: 9
nums.sort()           # in place, returns None
print(nums)           # [0, 1, 3, 4, 5]
sorted(nums, reverse=True)    # a new list: [5, 4, 3, 1, 0]
```

**Tuple** — ordered, immutable, hashable. Our [Python tuples guide](https://www.1stepgrow.com/articles/python-tuples) covers unpacking and named tuples in depth.

**Dictionary** — key-value lookup, insertion-ordered since Python 3.7:

```python
person = {"name": "Ananya", "age": 28}

person["city"] = "Pune"
person.get("phone")                # None instead of KeyError
person.get("phone", "unknown")     # 'unknown'
person.setdefault("tags", []).append("new")

for key, value in person.items():
    print(key, value)
```

The ordering guarantee is documented under [mapping types in the standard library reference](https://docs.python.org/3/builtins/stdtypes.html#typesmapping). Merging and comprehensions are also worth knowing:

```python
defaults = {"retries": 3, "timeout": 30}
config = defaults | {"timeout": 60}        # Python 3.9+
print(config)                              # {'retries': 3, 'timeout': 60}

squares = {n: n**2 for n in range(5)}      # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
```

**Set** — unordered, unique, fast membership:

```python
tags = {"python", "data", "python"}
print(len(tags))    # 2 - the duplicate is dropped; display order is not guaranteed

tags.add("ml")
other = {"data", "sql"}
tags & other        # {'data'} - intersection
tags | other        # union
tags - other        # difference
tags ^ other        # symmetric difference
```

The performance point is the one that matters in practice:

```python
big_list = list(range(1_000_000))
big_set = set(big_list)

999_999 in big_list     # O(n) - scans
999_999 in big_set      # O(1) on average - hashes
```

Python's own [TimeComplexity page](https://wiki.python.org/moin/TimeComplexity) lists these costs for CPython. So if you test membership repeatedly against the same collection, convert it to a set once. It is one of the easiest large speedups available.

## List vs dict vs set: a quick reference

| You need | Use | Example |
|---|---|---|
| Order, and to modify it | list | rows to process |
| A fixed record, or a dict key | tuple | `(lat, lon)` |
| Lookup by key | dict | user ID to profile |
| Uniqueness or fast membership | set | IDs already seen |

## How does control flow work in Python?

Control flow in Python is `if`/`elif`/`else`, `for` and `while` loops, and, since Python 3.10, `match`.

```python
score = 82
if score >= 90:
    grade = "A"
elif score >= 75:
    grade = "B"
else:
    grade = "C"
print(grade)    # B
```

Structural pattern matching is useful for dispatching on the shape of data, as the [match statement tutorial](https://docs.python.org/3/tutorial/controlflow.html#match-statements) shows:

```python
def dispatch(command):
    match command.split():
        case ["quit"]:
            return "bye"
        case ["load", filename]:
            return f"loading {filename}"
        case ["set", key, value]:
            return f"{key} = {value}"
        case _:
            raise ValueError(f"Unknown command: {command}")

dispatch("load sales.csv")    # 'loading sales.csv'
```

Loop idioms such as `enumerate` and `zip` are worth internalising:

```python
names = ["Ananya", "Rohit"]
scores = [91, 78]

for i, name in enumerate(names, start=1):    # index and value
    print(i, name)
for name, score in zip(names, scores):       # parallel iteration
    print(name, score)
```

Iterating a list by index — `for i in range(len(items))` — is nearly always a sign that `enumerate` or `zip` is the better tool.

The `else` clause on loops is obscure, yet occasionally perfect:

```python
for n in [3, 5, 7]:
    if n % 2 == 0:
        break
else:
    print("no even number found")     # runs only if the loop never broke
```

## When should you use a comprehension instead of a loop?

Use a comprehension to build a list, dict or set from another iterable in one readable line, and switch to a loop once the logic needs nesting plus conditions. A comprehension is the idiomatic way to build a collection:

```python
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
pairs = [(x, y) for x in "ab" for y in [1, 2]]
# [('a', 1), ('a', 2), ('b', 1), ('b', 2)]

emails = ["a@example.com", "b@example.org", "c@example.com"]
unique_domains = {email.split("@")[1] for email in emails}   # a set of 2 domains
lazy = (x**2 for x in range(1_000_000))     # generator - computed on demand
```

That last line is a generator expression; [iterators and generators](https://www.1stepgrow.com/articles/python-iterators-generators) explains why it costs almost no memory.

Know when to stop, though. Once there is nested filtering and a conditional expression, a loop is clearer:

```python
# too dense
result = [transform(x) if x.valid else fallback(x)
          for group in groups for x in group if x.enabled]

# clearer
result = []
for group in groups:
    for x in group:
        if not x.enabled:
            continue
        result.append(transform(x) if x.valid else fallback(x))
```

Readability is the point of comprehensions; past a certain complexity, they stop delivering it.

## What counts as True in Python?

Every object has a truth value. Empty collections, zero, `""` and `None` are falsy; almost everything else is truthy. The full list is in the [truth value testing reference](https://docs.python.org/3/builtins/stdtypes.html#truth-value-testing).

```python
bool([])        # False - empty sequences are falsy
bool([0])       # True  - non-empty, regardless of contents
bool(0)         # False
bool("")        # False
bool(None)      # False
```

As a result, the idiomatic emptiness check is:

```python
items = []
if not items:
    print("nothing to do")      # good
if len(items) == 0:
    print("nothing to do")      # works, less idiomatic
```

But be careful with values where `0` is meaningful — see the default-value caveat in our [Python operators guide](https://www.1stepgrow.com/articles/python-operators).

## Common mistakes with Python basics

These are the errors that come up again and again in beginner code. Each one follows from the rules above.

- **`{}` is an empty dict, not an empty set.** Use `set()` for an empty set; `type({})` is `<class 'dict'>`.
- **`list.sort()` returns `None`.** Writing `nums = nums.sort()` throws your list away. Use `sorted(nums)` when you want a new list back.
- **Modifying a list while looping over it.** Removing items mid-loop skips elements. Instead, build a new list with a comprehension.
- **Shared inner lists.** `[[0] * 3] * 3` and shallow copies both share nested objects, as shown earlier.
- **Mutable default arguments.** `def f(items=[])` shares one list across calls; the [functions guide](https://www.1stepgrow.com/articles/python-functions) shows the fix.

For example, here is the loop-removal bug in action:

```python
nums = [1, 2, 2, 3]
for n in nums:
    if n == 2:
        nums.remove(n)
print(nums)                            # [1, 2, 3] - one 2 survived

nums = [1, 2, 2, 3]
nums = [n for n in nums if n != 2]
print(nums)                            # [1, 3]
```

## What should you learn after Python basics?

With these Python basics in place, the next steps are [numeric types](https://www.1stepgrow.com/articles/python-numeric-data-types), [strings](https://www.1stepgrow.com/articles/python-strings), [functions](https://www.1stepgrow.com/articles/python-functions) and [exception handling](https://www.1stepgrow.com/articles/python-exception-handling). After that, move on to the data stack, starting with [NumPy](https://www.1stepgrow.com/articles/numpy-tutorial) and then pandas.



## Related reading

[Lists vs tuples in Python](https://www.1stepgrow.com/articles/lists-vs-tuples-python) and [Python strings](https://www.1stepgrow.com/articles/python-strings) go deeper on individual types.

## Frequently asked questions

### What are the core data structures in Python?

Lists for ordered mutable sequences, tuples for immutable records, dictionaries for key-value lookup, and sets for membership and uniqueness. Almost everything else in everyday Python, from JSON handling to pandas indexes, is built from or modelled on these four, so learning how each one behaves pays off for years.

### Why did changing one list change another?

Because assignment binds a name to the same object rather than copying it. After b = a, both names refer to one list, so a change through either name shows up in both. Use a.copy() for a shallow copy, or copy.deepcopy(a) when the contents are themselves mutable, such as a list of lists.

### When should I use a set instead of a list?

When you need uniqueness or fast membership testing. Checking x in some_set is O(1) on average, while x in some_list is O(n), so the difference becomes enormous at scale. The trade-off is that sets have no guaranteed order, cannot be indexed, and cannot hold unhashable items such as lists.

### Are dictionaries ordered in Python?

Yes. Since Python 3.7, dictionaries preserve insertion order as a language guarantee, not just a CPython implementation detail. Iterating a dict, or calling list() on it, returns keys in the order you added them. Updating an existing key keeps its original position, while deleting and re-adding a key moves it to the end.

---
_Source: 1stepGrow (https://www.1stepgrow.com/articles/python-basics/). Cite with the title, "1stepGrow" and a link._
