Python 3 is the latest version of Python, a popular programming language used mainly for web development, data science, and machine learning. Python has quickly become one of the most popular programming languages for new developers because of the wide range of uses in software development and some of the great improvements added since Python 2.
Today, we’ll walk you through a hands-on, challenge-based tutorial to get you started with Python quickly!
Our new 50 challenge Python course lets you skip the book work and get right to hands-on learning, with challenges ranging from beginner to expert difficulties.
Not everyone learns new programming languages the same way. For many aspiring Python programmers, book learning is inefficient and doesn’t lead to lasting learning. The main flaws of book learning are that it doesn’t invite the reader to directly engage and it encourages memorization instead of the natural exploration of programming concepts.
To avoid the downsides of book learning, many would rather learn to code by exploring Python code through hands-on, challenge-based crash courses. This method is great for hands-on learners because it allows absolute beginners to learn both the theory and pragmatic use case for every basic concept or algorithm.
The challenges encourage you to explore the problems naturally and learn to use Python syntax through investigation rather than lecturing.
print('hello world')
The print() function allows you to connect your program to the outside world. This function, as the name indicates, prints a value to the standard output.
You can think of the standard output as the environment in which your Python program lives. Your standard output is the air around you. Let’s say you shout “Ouch!” Every person in your environment can read from your standard output that you just experienced pain. The data that is printed to the standard output is of the string data type.
A string is a sequence of characters.
You can define a string in Python in any of the following ways:
'hello world')"hello world")'''hello world''' and """hello world""")In the puzzle, we use the single quote to define our string.
Challenge 1: Hello World
What is the output of the above code?
hello world
'hello world'
Hello World
x = 55 / 11print(x)
The puzzle has two goals.
First, it introduces the concept of variables. Python evaluates the result of the expression on the right side of the equation and stores it in the variable x. After defining the variable, you can access it at any point in the program code.
Second, it forces you to read code carefully due to a twist:
Division operations always return a floating-point number. Thus, variable x stores the float value 5.0. The print function outputs the result as a float and not as an integer value 5.
This is the source of most errors in the code. People focus too much on what they mean (semantics) and too little on how they say it (syntax).
Tip: Programs always execute instructions according to syntax, which may be different from what you intended. The key is to get used to thinking like a computer.
Challenge 2: Variables
What is the output of the above code?
5
05
5.0
Modern Python introduces features that make code cleaner, more expressive, and easier to read.
Learning these early helps developers write idiomatic, professional-grade Python from the start.
F-strings are the preferred way to format strings — they’re faster, more readable, and more concise than + concatenation or .format().
name = "Alice"print(f"Hello, {name}!")
Python supports simultaneous assignment and value unpacking, reducing boilerplate and improving readability.
a, b = 10, 20a, b = b, a # swap valuesx, y, *rest = [1, 2, 3, 4, 5]
This syntax is powerful for tuple unpacking, returning multiple values, and working with iterable data.
These built-in functions make iteration more Pythonic and expressive.
for i, val in enumerate(["a", "b", "c"]):print(i, val)for name, score in zip(names, scores):print(name, score)
enumerate() gives you both the index and value while looping.
zip() lets you iterate over multiple sequences together.
Introduced in Python 3.8, the walrus operator allows inline assignment inside expressions — reducing repetition and improving readability.
if (n := len(items)) > 5:print(f"List is too long ({n} elements)")
It’s especially handy in loops and conditionals where you need to compute and reuse a value immediately.
Added in Python 3.10, pattern matching simplifies complex conditional logic and improves clarity.
match status_code:case 200:print("OK")case 404:print("Not Found")case _:print("Unknown status")
This feature provides a cleaner alternative to long if-elif chains, especially when handling structured data or enums.
By embracing these modern features, Python developers can write shorter, clearer, and more maintainable code that aligns with today’s best practices.
squares = [1, 4, 9, 16, 25]print(squares[0])
The data type is “abstract” because you can use lists independently of the list elements’ concrete data types. Most complex algorithms that you’ll learn later will use lists as a building block. Many famous algorithms such as quicksort are based only on a single list as their core data structure.
The pythonic way of handling lists and list access is simple and clean. You can create a list by writing comma-separated values between the opening and closing square brackets.
lst = [1, 4, 9, 16, 25]
You access the i-th element in a list lst with the intuitive bracket notation lst[i]. This notation is consistent for all compound data types, such as strings and arrays.
Challenge 3: Lists
What is the output of the above code?
1
4
9
16
25
Learn Python concepts from data structures to decorators, all with step-by-step challenges. Educative’s hands-on courses let you learn the languages and tools you need quickly, with real-world examples and in-browser code environments.
word = "galaxy"print(len(word[1:]))
Slicing
Slicing is a Python-specific concept for accessing a range of values in sequence types, such as lists or strings. It is one of the most popular Python features. Understanding slicing is one of the key requirements for understanding most existing Python codebases.
The idea behind slicing is simple. Use the bracket notation to access a sequence of elements instead of only a single element. You do this via the colon notation of [start: end]. This notation defines the start index (included) and the end index (excluded).
Tip: A very common source of bugs is forgetting that the end index is always excluded in sequence operators.
For the sake of completeness, quickly look into the advanced slicing notation [start:end:step]. The only difference to the previous notation is that it allows you to specify the step size. For example, the command 'python'[:5:2] returns every second character up to the fourth character, i.e., the string pto.
len()
The len() function is a handy tool to get the length of built-in Python data types, such as strings, lists, dictionaries, or tuples.
Challenge 4: Slicing
What is the output of the above code?
galaxy
alaxy
5
6
Now we’re moving into some harder challenges. In this challenge, we’ll introduce branching and conditional statements.
def if_confusion(x, y):if x > y:if x - 5 > 0:x=yreturn "A" if y == y + y else "B"elif x + y > 0:while x > y: x -= 1while y > x: y -= 1if x == y:return "E"else:if x - 2 > y - 4:x_old = xx=y*yy = 2 * x_oldif (x - 4) ** 2 > (y - 7) ** 2:return "C"return "D"return "F"print(if_confusion(3, 7))
Just like any other programming language, Python also has Conditional Statements also known as Branching Statements. To create branches, you can use the keywords if, else, or elif. These statements return Booleans, true if the condition is met and false if it is not.
Tip: The computer does not execute the code strictly from top to bottom, and you shouldn’t either.
Instead, start where the program execution starts, which is at the bottom with the function call if_confusion(3, 7).
Now you know that x=3 and y=7. Then, you proceed to do what the interpreter does.
As x>y is false, you can skip the whole upper part of the function. Similarly, you can skip the if branch for x-2>y-4.
Challenge 5: Branching
What is the output of the above code?
A
B
C
D
E
F
Type hints make Python code more readable, maintainable, and self-documenting.
They work seamlessly with tools like MyPy, Pyright, and modern IDEs for autocomplete and static analysis — helping catch type errors before runtime.
You can specify expected argument and return types directly in function definitions:
def greet(name: str) -> str:return f"Hello, {name}"
This clearly communicates intent — name should be a string, and the function returns a string.
Add clarity by annotating collections such as lists, dictionaries, or tuples.
from typing import List, Dictdef process_scores(scores: List[int]) -> Dict[str, float]:return {"average": sum(scores) / len(scores)}
This helps IDEs provide better autocomplete and makes function contracts explicit.
When a value might be None or one of several types, use Optional or Union.
from typing import Optional, Uniondef find_item(data: dict, key: str) -> Optional[Union[str, int]]:return data.get(key)
Here, the return value can be a str, an int, or None, depending on what data.get() returns.
Improves readability and makes intent explicit.
Reduces runtime errors by catching mismatched types early.
Enhances IDE support with better autocomplete and refactoring.
Facilitates collaboration in larger codebases through clearer contracts.
Gradual typing means you can adopt type hints incrementally — start small and expand coverage over time for cleaner, safer Python code.
words = ['cat', 'mouse']for word in words:print(len(word))
Tip: Repeated code is redundant and hard to read, debug, and maintain. As programmers, we should avoid redundant code at all costs.
The Python for loop statement is a way out of redundant code. With a for loop, you write code once and put it into different contexts.
Among the ingredients that make a programming language powerful are control flow statements. The Python for loop is one such control flow statement. It repeats the execution of the code body for all sequence elements, iterating over all elements in the order of the sequence. It’s similar to a forEach loop found in Java or JavaScript.
In the puzzle, the variable word takes first the value cat and second the value mouse. We then print out the length of each word.
Challenge 6: For Loops
What is the output of the above code?
cat mouse
3 5
cat
3
Good code anticipates and handles failures gracefully.
Teaching robust error handling prepares learners for writing reliable, production-quality Python applications.
Handle predictable errors cleanly and provide fallback logic instead of crashing the program.
try:result = int(input("Enter a number: "))except ValueError:print("Invalid input. Please enter an integer.")
This approach keeps programs resilient and user-friendly.
Use raise to clearly signal unexpected or invalid conditions.
def divide(a: float, b: float) -> float:if b == 0:raise ValueError("Cannot divide by zero")return a / b
Intentional exceptions make debugging and error handling more explicit.
Assertions validate assumptions during development and testing.
assert len(data) > 0, "Data list cannot be empty"
Use them to catch logic errors early, but disable them in production for performance.
Always sanitize and validate user input before processing — it prevents crashes and mitigates security risks.
Validation ensures that only expected and safe data enters your program.
Python’s comprehension syntax and generator tools let you write clean, expressive, and efficient code.
Replace verbose loops with concise, readable expressions.
squares = [x**2 for x in range(10) if x % 2 == 0]
Quickly create collections from iterables.
squares_set = {x**2 for x in range(10)}squares_dict = {x: x**2 for x in range(10)}
Generate data lazily — without loading everything into memory.
gen = (x**2 for x in range(1_000_000))
Generators are ideal for large or streaming datasets.
Turn functions into iterators that produce values one at a time.
def fibonacci():a, b = 0, 1while True:yield aa, b = b, a + b
Generators and yield make it easy to handle large or infinite sequences efficiently — a cornerstone of Python’s functional and memory-safe programming style.
For our final challenge, we’ll take a look at a fundamental programming concept: functions.
def func(x):return x + 1f = funcprint(f(2) + func(2))
A function encapsulates a sequence of program instructions. The ideal function solves a single high-level goal.
For example, you can encapsulate the task of searching the web for specific keywords into a named function. This allows you to call for a search later using just a single statement.
Functions allow you to write code once and reuse it later or in other programs. Reusing code is more efficient than writing the same code each time. Suppose you want to calculate the square root of 145. You could either calculate it for the specific value 145 or define a function that calculates the square root for any value x.
Defining a function
You can define a function with the keyword def, followed by a name and the function’s arguments. The Python interpreter maintains a symbol table that stores all function definitions, i.e., mappings from function names to function objects.
This allows the interpreter to relate each occurrence of the function name to the defined function object. A single function object can have zero, one, or even many names.
In the puzzle, we assign the function object to the name func and then reassign it to the new name f. We can then use both the names to refer to the same code.
Upon the function call, the Python interpreter will find the function in the symbol table and execute it. This can make your code more readable when calling the same function in different contexts.
Challenge 7: Functions
What is the output of the above code?
6
3
3+3
import copya = [[1, 2], [3, 4]]shallow = a.copy()deep = copy.deepcopy(a)
Shallow copy creates a new outer object but keeps references to the same inner elements.
Deep copy recursively duplicates all nested objects.
Use copy.deepcopy() when you need complete independence between objects.
Avoid using mutable objects (like lists or dicts) as default parameters — they persist across function calls.
def append_to(lst=None):if lst is None:lst = []lst.append(1)return lst
Using None as the default ensures that each call creates a fresh list.
Remember that a = b makes both variables refer to the same object.
To create an independent copy, use .copy() or copy.deepcopy() when appropriate.
As Python projects grow, modularity and structure become essential for maintainability and scalability.
Split code into logical units using separate .py files.
Group related modules into packages (directories containing an __init__.py file).
This encourages reusability and cleaner architecture.
Prevent code from executing on import by using this guard:
if __name__ == "__main__":main()
This allows files to serve both as reusable modules and as standalone scripts.
Recursion is a fundamental technique for solving problems like factorials, Fibonacci sequences, or tree traversals.
def factorial(n: int) -> int:if n == 1:return 1return n * factorial(n - 1)
import unittestclass TestMath(unittest.TestCase):def test_addition(self):self.assertEqual(2 + 2, 4)
Embed simple tests directly in docstrings for small, self-contained functions.
def add(a, b):"""Return the sum.>>> add(2, 3)5"""return a + b
Run them using python -m doctest -v filename.py.
PEP 8 defines the official Python style guide — consistent naming, indentation, and spacing improve readability.
Automate style enforcement with tools like:
black – automatic code formatter
flake8 – linter for style and logical issues
isort – organizes imports
Use built-in debuggers like pdb or IDE debugging tools to inspect variables, trace execution, and step through code:
import pdb; pdb.set_trace()
Adopting testing, style checks, and debugging habits early helps students transition smoothly from classroom code to production-grade development.
Now that you’ve completed these 7 challenges, you have programming experience with the major building blocks of most Python 3 programs. Transitioning to a new language can be tough, but know that you’re making a great investment in your education. Python is the best general-purpose language in use today and there has never been a better time to start learning Python!
As you continue your journey to become a python programmer, look for Python projects on concepts like:
To help you get to that point, Educative has launched our new Python course An Intro to Python 3. This course features 50 challenges that provide hands-on experience with all the skills you’ll need to jump into building Python projects. The course’s challenge-based learning approach is perfect for developers looking to transition to Python because it draws on your existing coding intuition to learn in half the time.
Happy learning!