Nested Loops in Python

Nested Loops in Python

by Amanda Adoyi May 21, 2025 basics python

Nested loops in Python allow you to place one loop inside another, enabling you to perform repeated actions over multiple sequences. Understanding nested loops helps you write more efficient code, manage complex data structures, and avoid common pitfalls such as poor readability and performance issues.

By the end of this tutorial, you’ll understand that:

  • Nested loops in Python involve placing one loop inside another, enabling iteration over multiple sequences or repeated actions.
  • Situations where nested loops are a good idea include handling multidimensional data, generating patterns, and performing repetitive tasks with multiple layers of iteration.
  • You can break out of nested loops by using the break statement, which exits the innermost loop when a condition is met.
  • Disadvantages of nested loops include potential performance bottlenecks, poor readability, and variable scoping issues.

This tutorial provides practical examples and optimization techniques for using nested loops effectively in your Python programs.

Take the Quiz: Test your knowledge with our interactive “Nested Loops in Python” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Nested Loops in Python

Nested loops allow you to perform repeated actions over multiple sequences, but is there more? Test your understanding of nested loops in Python!

Getting Started With Nested Loops in Python

Loops are fundamental building blocks in programming, allowing you to iterate through actions efficiently. In Python, there are two primary types of loops: the for loop and the while loop. Both serve the same purpose —executing a block of code multiple times—but they differ in how they operate and in their use cases:

  • A for loop iterates over a sequence, such as a list or a range, and executes a block of code for each item. They’re useful when the number of iterations is known beforehand.
  • A while loop runs as long as a specified condition remains true, making it useful when the number of iterations isn’t known in advance.

You create a nested loop by placing one loop inside another. This structure is especially helpful when working with multidimensional data, generating patterns, or handling tasks that involve several layers of repetition.

In a nested loop, the first loop is called the outer loop, and the loop inside is the inner loop. So, for every iteration of the outer loop, the inner loop runs completely before the outer loop moves to the next iteration.

Here’s the basic syntax of a nested loop:

Python Syntax
for outer_variable in outer_iterable:
    for inner_variable in inner_iterable:
        <body>

The outer_iterable must be a list, a dictionary, or some other sequence of items that you can iterate over. The same applies to the inner_iterable. The <body> inside the inner loop contains the code that runs once for each for loop step in the inner_iterable. Since the inner loop is nested inside the outer loop, it runs in full for each iteration of the outer loop.

A good analogy for a nested loop is the hour and minute hands of a clock. The hour hand moves slowly around the clock, completing one full revolution every twelve hours. Meanwhile, the minute hand moves at a much faster rate, completing a revolution every hour. While both hands rotate at different speeds, they work together, each completing their own cycle within the same clock.

Here’s how the clock logic looks in Python code:

Python
>>> for hour in range(0, 24):
...     for minute in range(0, 60):
...         print(f"{hour:02d}:{minute:02d}")
...
00:00
00:01
00:02

23:57
23:58
23:59

As you can see, every time the minute hand completes a cycle, the hour hand moves to the next hour. :02d is a format specifier that ensures the number is printed as a two-digit-wide integer value.

Now that you’ve been introduced to nested loops, it’s time to explore some practical examples. You’ll become familiar with writing programs using nested loops in the following section.

Exploring Practical Examples of Nested Loops

As you just learned, nested loops have a number of use cases. Here, you’ll have a look at a few examples. These examples are interesting and practical, allowing you to have fun as you explore their syntax and semantics.

Printing Patterns With Nested Loops

Being able to print any pattern of your choosing is a fascinating feat in programming. One way you can achieve this is by understanding how nested loops work. The code snippet below builds a sail pattern using a few simple symbols. While this may not seem particularly exciting, consider it a first step toward creating something spectacular—like a spaceship:

Python
 1>>> height = 6
 2>>> sail_patterns = "*#-x+o"
 3>>> for row in range(height):
 4...     pattern = ""
 5...     spacing = " " * (height - row)
 6...     for symbol in sail_patterns:
 7...         pattern += symbol * row + spacing
 8...
 9...     print(pattern)
10...
11
12*     #     -     x     +     o
13**    ##    --    xx    ++    oo
14***   ###   ---   xxx   +++   ooo
15****  ####  ----  xxxx  ++++  oooo
16***** ##### ----- xxxxx +++++ ooooo

Here’s what the code does line by line:

  • Line 1: Sets the height variable, which is an indication of the size of the ship. It controls the number of rows in the pattern.
  • Line 3: Begins the outer for loop, iterating from 0 up to, but not including, height.
  • Line 5: Shifts the pattern to the right, decreasing spaces with increasing rows.
  • Line 6: Starts the inner for loop over the sail_patterns. It’s followed by the body of the loop, which updates the pattern.
  • Line 9: Prints the completed pattern.

This way, the spaceship is essentially built piece by piece. Adding one piece at a time, you could increase its size or expand its structure.

Generating a Multiplication Table

Multiplication tables display the results of numbers multiplied by one another. These tables are commonly used in grade school for quick reviews of multiplication operations. Nested for loops are a great way to generate multiplication tables in Python:

Python
>>> for multiplicant in range(1, 11):
...     for multiplier in range(1, 4):
...         expression = f"{multiplicant:>2d} × {multiplier}"
...         product = multiplicant * multiplier
...         print(f"{expression} = {product:>2d}", end="\t")
...     print()
 1 × 1 =  1      1 × 2 =  2      1 × 3 =  3
 2 × 1 =  2      2 × 2 =  4      2 × 3 =  6
 3 × 1 =  3      3 × 2 =  6      3 × 3 =  9
     ⋮                ⋮               ⋮
 8 × 1 =  8      8 × 2 = 16      8 × 3 = 24
 9 × 1 =  9      9 × 2 = 18      9 × 3 = 27
10 × 1 = 10     10 × 2 = 20     10 × 3 = 30

The example above displays the multiplication of numbers. The outer loop iterates through numbers 1 to 10, while the inner loop iterates through numbers 1 to 3.

For every iteration of the outer loop, the current value is multiplied by each number in the inner loop. After finishing one row, print() moves to the next line. This creates a structured multiplication table. You could give this a go yourself by using different ranges and printing the result.

In the next section, you’ll explore how to use nested loops to sum all the elements in a nested list.

Summing Elements in Multidimensional Lists

Another application of nested loops is processing elements in more than one dimension. A multidimensional or nested list is a list that contains other lists as its elements. To sum all elements in a nested list, you can use nested loops.

Staying true to the theme of space travel, you can picture nested list summation this way:

Python
>>> resource_donators = [
...     [8, 6, 3],
...     [9, 2, 7],
...     [4, 1, 5]
... ]
>>> total_resources = 0
>>> for planet in resource_donators:
...     for resource in planet:
...         total_resources += resource
...
>>> print(f"All resources gathered for interstellar travels: {total_resources}")
All resources gathered for interstellar travels: 45

Here, you use a nested for loop to handle resource collection from altruistic planets in a star system. Each sublist in resource_donators represents a planet, and each number in the sublist represents a unit of a resource—say, something known throughout the galaxy as foodonium. The total_resources is initialized to 0.

The outer loop iterates through each planet, and the inner loop goes through each individual resource, adding it to total_resources. Suppose that one planet provides 8, 6, and 3 units of foodonium. After processing all planets, the final total is printed, and the spaceship can continue on its way.

Next, you’ll take a look at how you can use nested loops to create pairwise combinations.

Creating Pairwise Combinations

When building programs, you may have to work with datasets that require you to make comparisons or work with data in pairs. In the example below, you’ll find a program that generates all possible matchups between players in a friendly game of space ball:

Python
>>> players = ["Bonnie", "Mike", "Raj", "Adah"]

>>> for player1 in players:
...     for player2 in players:
...         print(f"{player1} vs {player2}")
...
Bonnie vs Bonnie
Bonnie vs Mike
Bonnie vs Raj

Adah vs Bonnie
Adah vs Mike
Adah vs Raj
Adah vs Adah

With this approach, you can generate all possible matchups—including self-matchups—demonstrating that pairwise combinations are possible with nested loops. You may have noticed that players are allowed to play against themselves, which you might not want. Click the collapsible section below to learn how to get rid of self matchups in space ball.

You’ll have noticed that space ball allows players to be matched against themselves. Perhaps that’s just the nature of the game, or maybe you’d prefer to prevent that from happening. How could you remove self-matchups from the program above? One solution is to add an if condition to filter them out, as shown in the code below:

Python
>>> players = ["Bonnie", "Mike", "Raj", "Adah"]

>>> for player1 in players:
...     for player2 in players:
...         if player1 != player2:
...             print(f"{player1} vs {player2}")
...
Bonnie vs Mike
Bonnie vs Raj
Bonnie vs Adah
Mike vs Bonnie
Mike vs Raj
Mike vs Adah
Raj vs Bonnie
Raj vs Mike
Raj vs Adah
Adah vs Bonnie
Adah vs Mike
Adah vs Raj

This code example shows one way to generate pairwise combinations while filtering out self-matchups. This pattern comes in handy whenever you’re comparing elements in a group.

As you’ve seen, nested loops make it easy to create pairwise combinations, whether you’re including or filtering out matches. This isn’t only useful in games, but also in real-world scenarios like comparing data entries. With that under your belt, you’ll now take a look at an example that uses while loops in nested situations.

Working With while Loops in Nested Structures

So far, you’ve seen several examples using for loops in nested loops. There are also a number of situations where you’ll prefer to use while loops instead. For example, say that you’re building an app that prints each letter of a word:

Python
>>> while True:
...     word = input("Enter a word (or type 'exit' to stop): ")
...
...     if word == "exit":
...         break
...
...     for letter in word:
...         print(letter)
...
Enter a word (or type 'exit' to stop): foodonium
f
o
o
d
o
n
i
u
m
Enter a word (or type 'exit' to stop): exit
>>>

The example above shows a program that allows a user to type a word at the prompt, with each letter of the word eventually printed to the screen. A break keyword is added to halt the execution of the program when exit is typed.

Whether you’re using for loops or while loops in nested loops, you need to be cautious when using them. Poor usage can lead to a number of problems, such as reduced readability and performance issues, as you’ll soon see.

Avoiding Common Nested Loop Pitfalls

When used improperly, nested loops can cause more problems than solutions. It’s therefore crucial to be aware of potential pitfalls you may encounter when using them to build programs, including:

  • Variable scoping issues
  • Poor readability
  • Performance bottlenecks

In the next sections, you’ll have a closer look at each of these pitfalls and investigate ways to prevent them.

Handling Variable Scoping Issues

In Python, variables defined within a loop are accessible in the enclosing scope. This means that outer loop variables can be used inside the inner loop. However, unintended side effects may occur when an outer loop variable is modified in the inner loop, or when they both use the same variable.

If you forget to keep track of such cases, you may be forced to deal with bugs later on, which could be a real hassle. Consider the following code:

Python
>>> employees = [("Dorothy", "DevOps"), ("Abdel", "HR"), ("Nataliya", "DevOps")]
>>> departments = [
...     {"name": "DevOps", "city": "Berlin"},
...     {"name": "HR", "city": "Abuja"},
... ]

>>> for name, department in employees:
...     for department in departments:
...         if department["name"] == department:
...             print(f"{name} works in {department['city']}")
...

What you’ll observe here first is that department refers to the department name associated with an employee, as defined by the outer for loop. However, the inner for loop also refers to a department, which is a value in a list of dictionaries. The original definition of department by the outer loop is overwritten by the inner loop, causing logical errors.

One way to solve this is to declare different variable names for inner and outer loops:

Python
>>> for name, department in employees:
...     for dept in departments:
...         if dept["name"] == department:
...             print(f"{name} works in {dept['city']}")
...
Dorothy works in Berlin
Abdel works in Abuja
Nataliya works in Berlin

Now you can be certain that department and dept are handled separately. In order to avoid problems such as this, you should always avoid reusing the same variable name in both outer and inner loops, and be cautious when modifying an outer loop variable inside an inner loop.

Addressing Poor Readability

A major challenge when working with nested loops is poor readability. As the complexity of a program increases, deeply nested loops can make code difficult to read and maintain. While nested loops are sometimes necessary, excessive nesting can make it harder for other developers—or even for you—to understand what’s going on in your code.

Consider the following example, which is a search protocol for when an intruder ventures into your ship:

Python
>>> living_quarters = 3
>>> sections = 2
>>> floors = 3

>>> for floor in range(floors):
...     for section in range(sections):
...         for living_quarter in range(living_quarters):
...             print(
...                 f"Scanning quarter {living_quarter} in section {section}"
...                 f" on floor {floor} for the intruder..."
...             )
...
Scanning quarter 0 in section 0 on floor 0 for the intruder...
Scanning quarter 1 in section 0 on floor 0 for the intruder...
Scanning quarter 2 in section 0 on floor 0 for the intruder...
...
Scanning quarter 0 in section 1 on floor 2 for the intruder...
Scanning quarter 1 in section 1 on floor 2 for the intruder...
Scanning quarter 2 in section 1 on floor 2 for the intruder...

At first glance, this code may seem manageable, even though there are several areas to search through. However, you might be taken aback by the number of lines of output in the terminal when you run the program.

Now imagine being on an enterprise-level ship, with even more levels of nesting introduced. A piece of code with four or five nested loops can quickly become unreadable and difficult to maintain, making debugging a waking nightmare.

One reason why nested loops in Python are notorious for poor readability is that Python is an indented language, in contrast to a language like JavaScript that relies on curly brackets to distinguish code blocks. Another challenge is that it can become difficult to keep track of which loop is controlling which part of the logic. These factors combined make readability a crucial consideration when working with nested loops.

Another important consideration when it comes to nested loops is how they affect performance. You’ll explore this next.

Dealing With Performance Bottlenecks

To understand how poorly written nested loops can affect performance, it helps to briefly explore the concept of time complexity. Time complexity is a way to measure how fast an algorithm runs relative to the rate at which the input size grows, denoted by O(n).

The O represents Big O notation, where n is the size of the input. It’s a widely used measure of performance, grouped into complexities, arranged in order from most to least efficient:

Complexity Big O Description
Constant Time O(1) Runtime doesn’t change with input size
Logarithmic Time O(log n) Runtime grows slowly as input size increases
Linear Time O(n) Runtime grows directly with input size
Quadratic Time O(n2) Runtime grows with the square of the input size
Exponential Time O(2n) Runtime doubles with each additional input
Factorial Time O(n!) Runtime grows extremely fast (factorial growth)

A single for loop runs in O(n) time. While this may not seem too bad for performance, imagine having six layers of nested loops. In that case, the time complexity is O(c × r × a × w × l), where each letter represents a loop. That’ll lead to performance bottlenecks. So you can see why nested loops quickly become inefficient, especially for large programs where each loop increases the time complexity.

The example below shows an algorithm for detecting runaway clones aboard your ship—an unfortunate side effect of a well-meaning science experiment:

Python
>>> crew_ids = [1, 2, 8, 4, 5, 6, 7, 8, 9, 10]
>>> for i, first_id in enumerate(crew_ids):
...     for second_id in crew_ids[i + 1:]:
...         if first_id == second_id:
...             print(f"Clone found: id={first_id} is a duplicate.")
...
Clone found: id=8 is a duplicate.

As you can see, there are only two for loops, so the time complexity may be O(c × r) denoted as O(n2).

For a small example like this, you may get away with it, but for a large enough input, it would negatively affect performance. Paying attention to performance would help you write more efficient algorithms.

This code has a fun narrative of identifying duplicate clones on a ship. However, in reality, it’s identifying duplicate numbers in a list. It goes through each first_id one by one while keeping track of its location using enumerate(). If any of the numbers in the list match, then you identify the number as a duplicate. More interestingly, you capture a clone.

A solution to such a problem may be to forego nested loops in preference for more optimized techniques. In the example above, you could use a Python set or collections.Counter instead of nested loops. In both cases, the time complexity amounts to O(n), which has a better performance than O(n2).

In the code below, you can see how this works with a set:

Python
>>> crew_ids = [1, 2, 8, 4, 5, 6, 7, 8, 9, 10]
>>> detected = set()
>>> for crew_id in crew_ids:
...     if crew_id in detected:
...         print(f"Clone found: id={crew_id} is a duplicate.")
...     else:
...         detected.add(crew_id)
...
Clone found: id=8 is a duplicate.

This next example shows how you can detect duplicates using a Counter:

Python
>>> from collections import Counter
>>> crew_ids = [1, 2, 8, 4, 5, 6, 7, 8, 9, 10]
>>> detected = Counter(crew_ids)
>>> for key, value in detected.items():
...     if value > 1:
...         print(f"Clone found: id={key} is a duplicate.")
...
Clone found: id=8 is a duplicate.

Here, Counter detects the duplicates for you. Keep in mind that you don’t have to let go of nested loops entirely in some situations. Nested loops can be optimized, and you’ll learn exactly how next.

Optimizing Nested Loops in Python

As you’ve seen, nested loops offer powerful capabilities, but like many useful tools, they also have their downsides. You explored some of those limitations in the previous section. In this section, you’ll learn how to optimize nested loops so you can use them more efficiently in your programs.

Using break and continue

Using nested loops may sometimes result in unnecessary iterations. You can control these iterations better by using the break and continue statements.

If a nested for loop were a BLT sandwich, using break would be like eating the top layers and discarding the rest. Using continue, on the other hand, would be like eating the top layers, skipping the lettuce, then finishing the bottom layers. The continue statement skips unnecessary iterations, reducing workload and improving performance.

In other words, the break statement lets you exit a loop before it finishes, and the continue statement lets you skip iterations that aren’t needed. If you were an astronaut floating in zero gravity who hated lettuce, a program for how you eat a massive BLT sandwich might look like this:

Python
>>> blt_sandwich = [
...     ["bread", "lettuce", "tomato", "bacon"],
...     ["bread", "bacon", "lettuce", "tomato"],
...     ["bacon", "bacon", "tomato", "lettuce"]
... ]
>>> target = "bacon"
>>> found = False
>>> for layer in blt_sandwich:
...     for ingredient in layer:
...         if ingredient == target:
...             print(f"Found the crispy {target}!")
...             found = True
...             break
...
...     if found:
...         print("Enjoying the crunch and worth it.")
...         break
...
Found the crispy bacon!
Enjoying the crunch and worth it.

If you were to keep running the program even after finding your target bacon, the program would use up more resources. The break statement fixes this by terminating the loop early.

Nested for loops can be optimized with continue like in this example:

Python
>>> blt_sandwich = [
...     ["bread", "lettuce", "tomato", "bacon"],
...     ["bread", "bacon", "lettuce", "tomato"],
...     ["bacon", "bacon", "tomato", "lettuce"]
... ]
>>> target = "bacon"
>>> found = False
>>> for layer in blt_sandwich:
...     for ingredient in layer:
...         if ingredient != target:
...             print(f"This is not bacon. Skipping...")
...             continue
...         print(f"Found the crispy {target}!")
...         found = True
...         break
...
...     if found:
...         print("Enjoying the crunch and worth it.")
...         break
...
This is not bacon. Skipping...
This is not bacon. Skipping...
This is not bacon. Skipping...
Found the crispy bacon!
Enjoying the crunch and worth it.

The program skips non-bacon layers in the BLT, reducing unnecessary processing. By handling it this way, the code becomes more efficient and avoids extra workload. Both break and continue statements help improve the efficiency of nested loops, especially in tasks that involve searching or filtering.

Replacing Nested Loops With List Comprehension

With list comprehensions, you can replace the intimidating hill of nested loops with a more compact and readable expression. While list comprehensions don’t necessarily improve performance or reduce time complexity—especially in nested iterations—they often make your intent clearer and your code easier to understand.

The following example illustrates this with a crewmate determined to make towering stacks of pancakes:

Python
>>> bases = ["plain", "chocolate", "blueberry"]
>>> toppings = ["honey", "whipped cream", "alien syrup"]
>>> pancake_stacks = []
>>> for base in bases:
...     for topping in toppings:
...         pancake_stacks.append(f"{base.capitalize()} pancake with {topping}")
...
... print(pancake_stacks)
...
['Plain pancake with honey',
'Plain pancake with whipped cream',
'Plain pancake with alien syrup',
'Chocolate pancake with honey',
'Chocolate pancake with whipped cream',
'Chocolate pancake with alien syrup',
'Blueberry pancake with honey',
'Blueberry pancake with whipped cream',
'Blueberry pancake with alien syrup']

This is an example of a traditional nested for loop. You’ll notice that the outer and inner loops use different variable names. This is an important detail that helps you avoid the variable scoping issues you learned about earlier.

Below, you’ll find the same program, but this time using list comprehension for the nested loops:

Python
>>> bases = ["plain", "chocolate", "blueberry"]
>>> toppings = ["honey", "whipped cream", "alien syrup"]
>>> pancake_stacks = [
...     f"{base.capitalize()} pancake with {topping}"
...     for base in bases
...     for topping in toppings
... ]
>>> print(pancake_stacks)
['Plain pancake with honey',
'Plain pancake with whipped cream',
'Plain pancake with alien syrup',
'Chocolate pancake with honey',
'Chocolate pancake with whipped cream',
'Chocolate pancake with alien syrup',
'Blueberry pancake with honey',
'Blueberry pancake with whipped cream',
'Blueberry pancake with alien syrup']

Note that while list comprehensions may look clean and concise, they aren’t always the better choice. Readability can suffer when the logic becomes too complex, especially with nested structures. This goes against the Zen of Python, which emphasizes that readability is important.

So, the question boils down to whether list comprehension is actually more efficient than nested loops. A good answer to that is this—it depends. For straightforward transformations or filtering, list comprehensions are often ideal. But when your logic involves deep nesting, a traditional loop may be better. Ultimately, the key is to strike a balance between readability and efficiency.

The optimization techniques you’ve explored may appear to be minor modifications that make nested loops better, but in larger applications, they can significantly impact performance. That’s why it’s important to recognize when nested loops are unnecessary—and, when you do use them, to manage them thoughtfully and efficiently.

Conclusion

Nested loops are a powerful tool in Python. With them, you can handle complex iterations and work through difficult algorithms. They’re well-suited for tasks like handling multidimensional data, searching, and returning structured outputs such as custom patterns. However, when used poorly, they can lead to problems with performance, maintainability, and readability.

In this tutorial, you’ve learned how to:

  • Write Python programs to handle multidimensional data using nested loops
  • Use nested for and while loops effectively
  • Optimize nested loops by using break, continue, and list comprehension in looping structures
  • Solve practical problems such as printing patterns and processing data

Understanding nested loops helps you write more efficient code, manage complex data structures, and avoid common pitfalls such as poor readability and performance issues.

Frequently Asked Questions

Now that you have some experience with nested loops in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.

These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.

You use nested loops in Python to run a loop inside another loop, allowing you to iterate over multiple sequences or perform repeated actions within each iteration of the outer loop.

You’ll find nested loops useful for handling multidimensional data, generating patterns, or performing repetitive tasks that require multiple layers of iteration.

You can break out of nested loops using the break statement to exit the current loop.

Nested loops can lead to poor readability, variable scoping issues, and performance bottlenecks, especially when they’re deeply nested or when you’re handling large datasets.

Take the Quiz: Test your knowledge with our interactive “Nested Loops in Python” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Nested Loops in Python

Nested loops allow you to perform repeated actions over multiple sequences, but is there more? Test your understanding of nested loops in Python!

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Amanda Adoyi

Amanda is a Software Developer and all-round nerd who loves to play around with code and build projects in her free time.

» More about Amanda

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!