The Method Behind the Madness: Why Python’s Most “Annoying” Features Are Actually Genius

If you have ever migrated to Python from another programming language like C++, Java, or JavaScript, you have probably had a moment of pure, unadulterated frustration.

You sit down to write a simple counting loop, and instead of a clean, mathematical statement, you are forced to write for i in range(5). Then you discover that range(1, 5) doesn’t actually count to 5. And don’t even get started on the older developers who still grumble about adding parentheses to print() statements.

At first glance, Python can feel like it’s being difficult just for the sake of it. But if you dig into the history of the language, you’ll find that these design quirks aren’t mistakes—they are the result of deliberate, brilliant engineering choices.

Here is the secret history behind why Python’s loops and print functions work the way they do.

1. The range() Riddle: Python Only Loops Over Collections

In traditional languages, a loop is basically a manual counter. You tell the computer where to start, when to stop, and how much to add each time:

C

for (int i = 0; i < 5; i++) {
    // Do something
}

Python throws this entire concept out the window. Python does not have traditional counting loops. It only has “for-each” loops.

Python’s loop is designed to do one thing: step through a collection of items until it hits the end. The secret to understanding this lies entirely in a single keyword: in.

When you want to look at a list of items, the syntax is beautifully readable because you are physically looking inside a collection:

Python

fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

Edit in Code Lab>>

But what happens if you don’t have a list of items, and you just want to run a piece of code 5 times? Because Python refuses to manage a manual counter, you have to feed it a collection anyway.

This is where range() comes in. range() isn’t a loop command; it is just a factory that builds a collection-like sequence of numbers. When you write:

Python

for i in range(5):
    print(i)

Edit in code lab>>

You aren’t changing how the loop works. The loop is still using that same in keyword to step through a collection. You have just swapped out a collection of words (fruits) for a sequence of numbers generated by range(5) (which acts like [0, 1, 2, 3, 4]).

The Educational Roots

This design came from ABC, a language Python’s creator, Guido van Rossum, worked on in the 1980s. ABC was built to teach non-programmers how to code. User testing revealed that human brains naturally think about things (collections) rather than memory registers and counter increments. Python inherited this philosophy: the for...in syntax stays identical because, to Python, a list of data and a range() of numbers are fundamentally the exact same thing.

2. Why Does range(1, 5) Exclude the 5?

The next biggest headache for Python newcomers is the “exclusive” upper bound. Why does range(1, 5) output 1, 2, 3, 4 but leave out the 5?

For this, we have to thank computer science royalty. In 1982, legendary computer scientist Edsger Dijkstra wrote a paper arguing that the ultimate way to express a range of numbers is the half-open interval (where the starting number is included, but the ending number is excluded).

Python adopted this for three highly practical reasons:

  • Zero-Based Indexing: Python starts counting lists at 0. If you want to loop through a 5-item list, range(5) gives you 0, 1, 2, 3, 4. These are the exact index positions of your items.
  • Flawless Math: The number of items in your sequence is always exactly stop - start. For example, range(2, 7) generates exactly 7 - 2 = 5 numbers.
  • Consistency with Slicing: Slicing a list uses the same logic. my_list[0:3] grabs three items. Making range(0, 3) match this behavior keeps the language unified.

3. The Great print Evolution: Statement vs. Function

If you look at old Python 2 tutorials, you’ll see code like print "Hello, World". But in Python 3, omitting the parentheses results in a SyntaxError.

Turning print from a statement into a built-in function was one of the most controversial changes in Python’s history, but it unlocked massive amounts of power.

Why the Parentheses Matter

In Python 2, print was a hardcoded keyword, much like if or for. Because it wasn’t a regular function, customizing it required bizarre, hacky syntax. If you didn’t want a newline character at the end of your text, you had to add a random trailing comma.

By making print() a function in Python 3, developers gained clean flexibility via keyword arguments:

Python

# Want to change the ending? Easy.
print("Hello", end=" ")

# Want to separate items with commas? Clean.
print("apple", "banana", "cherry", sep=", ") 

Edit in code lab>>

Furthermore, because print() is now a regular function, it is a “first-class citizen.” You can pass it as an argument to other functions, or completely override it to build custom logging systems. In Python 2, the print keyword was completely untouchable.

The Ultimate Payoff

Python’s core philosophy is famously summarized in The Zen of Python: “There should be one—and preferably only one—obvious way to do it.”

While range() and print() might feel slightly strange when you are typing your first few lines of code, they exist to protect you from the single most common bug in programming history: the off-by-one error. By forcing consistency and treating loops as item-inspectors rather than math counters, Python ensures your code stays readable, predictable, and clean.

Author