
Every January, a student raises a hand in the first Python class and asks why they have to press Tab so much. The question is not about the keyboard. It's about indentation, and it's the first wall most beginners hit. Over the next few pages we'll look at four ideas that explain most of the errors people make in their first month: indentation, what a variable actually is, why lists behave the way they do, and how a for loop really thinks.
Indentation is the block structure
In most languages, blocks of code are marked with braces. In Python, the block is marked by the indentation itself. The interpreter looks at how far a line is indented to know which block it belongs to. When you write:
age = 17
if age >= 18:
print("You can vote.")
else:
print("Not yet.")
The print line is indented four spaces, so it belongs to the if. If you remove the indentation, Python raises an IndentationError and points a caret at the line. The interpreter is telling you the line is in the wrong place, not that you misspelled something.
The mistake we see every single batch is mixing tabs and spaces. The editor shows both as the same width, so the code looks fine. Python counts them as different characters and refuses to run. The fix is to configure your editor to insert spaces when you press Tab, and to never use Tab where spaces should go. Every serious code editor has this setting; IDLE does it by default.
A smaller but common problem is over-indenting. Beginners press Tab twice because they think more indentation means more importance. It doesn't. One level of indentation means one block. Two levels mean a block inside a block, like a for loop inside an if. When the indentation does not match the logic, the program either fails or does something quietly wrong. The colon at the end of the if and else lines tells Python a block is coming. Forget the colon and you get a SyntaxError at that exact line, which is at least easy to find.
Variables are names, not boxes
Most people picture a variable as a box with a value inside. That mental model works for a week and then breaks. In Python, a variable is a name that points to an object. When you write:
x = 5
x = x + 1
x = "hello"
You are not changing the box. You are pointing the name x at three different objects in sequence: first the integer 5, then the integer 6, then the string "hello". The old objects are not destroyed; they just lose one reference. If nothing else points to them, Python's garbage collector cleans them up.
The practical consequence is that Python does not force a variable to keep one type. A student last June wrote a program that read user input, did some arithmetic, and then assigned the result to a variable that previously held a string. The program worked. The student was confused because their textbook said variables have types. The textbook was talking about static languages. Python is dynamic: the variable has no type, only the object it currently points to has one. The line x = x + 1 reads the current object, computes a new one, and rebinds the name. It never modifies the object itself, because integers are immutable.
This distinction becomes important when you pass a variable to a function. If the function reassigns its own parameter, the caller's variable is not affected. But if the object is mutable, the function can change the object itself, and the caller sees the change. That brings us to lists.
Lists are mutable, and that changes everything
A list is a mutable object. You can add to it, remove from it, or change an element. That is useful and also the source of a classic confusion. Consider:
original = [1, 2, 3]
copy = original
copy.append(4)
print(original) # [1, 2, 3, 4]
The beginner expects original to stay [1, 2, 3]. It doesn't. The line copy = original does not create a new list. It creates a second name that points to the same list object. Both names see the same list. The append happens on the object, so both names reflect it.
The same thing happens when you pass a list to a function. If the function does something like:
def add_item(items, item):
items.append(item)
then calling add_item(my_list, 5) changes my_list in place. The function did not return anything, but the caller's list grew. This surprises people who came from a language where arguments are copied. The rule is simple: assignment copies the reference, not the object. To actually copy the list, use list(original) or original[:]. Both create a new list object with the same elements.
This is not a bug. It is the reason Python can handle large lists efficiently. Copying a list of a million numbers every time you pass it would be slow. But the behavior forces you to think about who owns the object and whether you want to mutate it or create a new one. When we tell students this, about half the class has a small crisis. The other half nods and then makes the mistake next week anyway.
The for loop is a habit, not a rule
The most common way beginners write a loop is with a counter. They write for i in range(10) and then use i to index into a list. That works. But Python's for loop is built to iterate over the elements themselves, not over positions. If you have a list of names, you can write:
for name in names:
print(name)
No index, no len(), no range. The loop takes each element in order and binds it to the name variable. This is clearer and faster to write.
You still need range sometimes. When you want to run a block a fixed number of times, or when you genuinely need the position, range is the tool. And when you need both the position and the element, use enumerate:
for index, name in enumerate(names):
print(index, name)
The comma in the print call is a small detail: print(1, 2) prints "1 2". The enumerate function returns pairs, and the for loop unpacks each pair into index and name. This is the version we recommend for anything where you need to know which position you are at.
A mistake we see in week two is trying to change a list while looping over it. If you remove elements from a list while iterating, the loop skips some because the positions shift. The honest answer is that you should not mutate a list while iterating over it. Collect what you want to keep in a new list and assign that back. It takes an extra line and saves an hour of debugging.
Common questions
Should I use Python 2 or Python 3?
Python 2 stopped being maintained in January 2020. Any tutorial, library, or job posting from the last few years is talking about Python 3. Install the latest Python 3 release from python.org and ignore anything that mentions Python 2 unless you are maintaining a very old system.
What is the difference between a list and a tuple?
A list is mutable: you can append, remove, and change elements. A tuple is immutable: once you create it, you cannot change it. You write a tuple with parentheses, like (1, 2, 3), and a list with square brackets. Use a tuple when the sequence should not change, like a point in 2D space or a fixed set of options.
When should I write a function instead of just repeating code?
If you have the same three lines in two places, write a function. If you have them in one place and nowhere else, a function is optional. The real test is whether the logic has a name you can say out loud, like "clean_text" or "average". If it does, a function usually helps you read the code later, even if you never call it twice.
Should I install Anaconda or plain Python?
If you are following a course that tells you to install Anaconda, install Anaconda. If you are starting on your own, plain Python plus a text editor like VS Code is simpler and teaches you what is actually installed. Anaconda bundles many scientific libraries and its own package manager, which is convenient but hides some details. Ask your instructor which one the course expects.
Courses this relates to
Come and sit a class before you decide
Reading about it only gets you so far. Pick a day, sit in on a class that is already running, write some code, and talk to the students in it. Free, and nothing to pay afterwards unless you want to join.