PYnative

Python Programming

  • Learn Python
    • Python Tutorials
    • Python Basics
    • Python Interview Q&As
  • Exercises
    • Python Exercises
    • C Programming Exercises
    • C++ Exercises
  • Quizzes
  • Code Editor
    • Online Python Code Editor
    • Online C Compiler
    • Online C++ Compiler
Home » Python Exercises » Intermediate Python Exercises: 65 Coding Problems with Solutions

Intermediate Python Exercises: 65 Coding Problems with Solutions

Updated on: January 7, 2026 | 5 Comments

Python is widely used and regarded as the most beginner-friendly programming language. However, to truly master it, you need to go beyond the basics and solve intermediate and advance coding problems. Also, you need to learn how to use advanced data structures, functional programming, and object-oriented design.

In this article, you’ll find a list of 65 intermediate Python coding questions primarily focuses on loops, list, dictionary, sets, OOP, decorators, Generators (yield), , and strings, multi-threading, List/Dict comprehensions, and Lambda functions, File Handling, regular expression, Date and Time, other miscellaneous topics.

These coding problems will improve your logic and coding skills, helping you write cleaner code and prepare for Python interviews.

What to Expect in These Exercises

Each exercise is structured to help you understand not just the how, but the why behind the code. Every problem includes:

  • Practice Problem: You’ll get a clear description of what code you need to write.
  • Exercise Purpose: You’ll learn why this skill is useful in real-world software development.
  • Given Input and Expected Output: You’ll see examples to help you check your logic.
  • Pythonic Hints: You will get a suggestion to guide you if you are stuck. These hints may point to a built-in function, module, algorithm, or concept, and will help you break down the problem without giving away the answer.
  • Detailed Explanation: After you solve the problem, you will see a step-by-step breakdown of how the solution works and which Python concepts are used.
  • Use the Online Python Code Editor to try out your solutions.

Also, See Python Exercises: 20 topic-wise sets and 385+ coding questions to practice.

+ Table of Contents (65 Exercises)

Table of contents

  • Exercise 1: List Comprehension Mastery
  • Exercise 2: Dictionary Merging with Logic
  • Exercise 3: Frequency Map with Counter
  • Exercise 4: Anagram Checker
  • Exercise 5: Flatten a Nested List
  • Exercise 6: Reverse Each Word of a String
  • Exercise 7: Palindrome Sentence
  • Exercise 8: List Comprehension Filtering (Advanced)
  • Exercise 9: Remove Duplicates (Preserving Order)
  • Exercise 10: Circular Shift (Rotation)
  • Exercise 11: Dictionary Merging (Value Grouping)
  • Exercise 12: Inverted Index
  • Exercise 13: Dictionary Sorting (Lambda)
  • Exercise 14: Subset and Superset Validation
  • Exercise 15: Set Symmetric Difference
  • Exercise 16: Power Set Generation
  • Exercise 17: Age Calculator (Exact)
  • Exercise 18: Countdown Timer (Time Delta)
  • Exercise 19: Business Days Calculator
  • Exercise 20: Custom Iterator Class
  • Exercise 21: Find Duplicates in O(n) Time
  • Exercise 22: Singly Linked List Implementation
  • Exercise 23: Stack Implementation (LIFO)
  • Exercise 24: Queue Implementation (FIFO)
  • Exercise 25: Recursive Binary Search
  • Exercise 26: Lambda Sorting with Tuples
  • Exercise 27: Map and Filter Combination
  • Exercise 28: Custom @timer Decorator
  • Exercise 29: Fibonacci Generator (Memory Efficiency)
  • Exercise 30: Custom Context Manager ( with statement)
  • Exercise 31: The Versatile *args and **kwargs
  • Exercise 32: Zip and Enumerate Mapping
  • Exercise 33: Memoization with lru_cache
  • Exercise 34: Set Operations for Data Analysis
  • Exercise 35: Inheritance and Method Overriding
  • Exercise 36: Encapsulation (Private Attributes)
  • Exercise 37: Property Decorators ( @property )
  • Exercise 38: Class Methods vs. Static Methods
  • Exercise 39: Magic Methods ( __str__ and __add__ )
  • Exercise 40: Abstract Base Classes (ABC)
  • Exercise 41: Multiple Inheritance and MRO
  • Exercise 42: Composition over Inheritance
  • Exercise 43: The Singleton Pattern
  • Exercise 44: Data Classes ( @dataclass )
  • Exercise 45: CSV Processor
  • Exercise 46: JSON Parser and Nested Access
  • Exercise 47: Regular Expressions (Email Extractor)
  • Exercise 48: Robust Error Handling (Try/Except/Finally)
  • Exercise 49: OS Module (File Searcher)
  • Exercise 50: Multi-threading (Concurrency)
  • Exercise 51: API Requests with Error Handling
  • Exercise 52: Word Frequency Counter
  • Exercise 53: Student Management System
  • Exercise 54: Password Strength Checker
  • Exercise 55: Number Guessing Game (Computer vs User)
  • Exercise 56: File Word Count Tool
  • Exercise 57: Prime Number Generator
  • Exercise 58: Email Validation Program
  • Exercise 59: File-Based To-Do List App
  • Exercise 60: Random Password Generator
  • Exercise 61: Pascal’s Triangle
  • Exercise 62: Smart Property Decorators
  • Exercise 63: Caesar Cipher Implementation
  • Exercise 64: Hollow Triangle Pattern
  • Exercise 65: Solid Diamond Pattern

Exercise 1: List Comprehension Mastery

Practice Problem: Write a single-line list comprehension that takes a list of strings, filters out strings shorter than 4 characters, and converts the remaining strings to uppercase.

Exercise Purpose: List comprehensions are a hallmark of Pythonic code. They allow you to replace verbose for loops and .append() calls with a readable, optimized single line. This exercise teaches you how to combine transformation (uppercase) and filtering (length check) in one expression.

Given Input: words = ["apple", "bat", "cherry", "dog", "elderberry"]

Expected Output: ['APPLE', 'CHERRY', 'ELDERBERRY']

+ Hint

The syntax for a list comprehension with a filter is [expression for item in iterable if condition].

+ Show Solution
words = ["apple", "bat", "cherry", "dog", "elderberry"]

# Single line transformation and filtering
filtered_words = [w.upper() for w in words if len(w) >= 4]

print(f"Original: {words}")
print(f"Result:   {filtered_words}")Code language: Python (python)

Explanation of Solution:

  • for w in words: This iterates through each element in the original list.
  • if len(w) >= 4: This acts as a gatekeeper, only allowing strings with 4 or more characters to pass to the expression phase.
  • w.upper(): This is the transformation applied to every item that passes the filter. The result is a brand-new list, leaving the original words list untouched.

Exercise 2: Dictionary Merging with Logic

Practice Problem: Write a function that merges two dictionaries. If a key exists in both dictionaries, sum their values. If a key exists in only one, include it as is.

Exercise Purpose: Real-world data often comes from multiple sources. Simply using dict.update() would overwrite duplicate keys. This exercise introduces you to efficient dictionary iteration and the dict.get(key, default) method, which is essential for avoiding KeyError.

Given Input: dict_a = {'a': 10, 'b': 20} dict_b = {'b': 5, 'c': 15}

Expected Output: Merged Dictionary: {'a': 10, 'b': 25, 'c': 15}

+ Hint
  • Start with a copy of the first dictionary.
  • Iterate through the second dictionary and use the += operator to add values to existing keys.
+ Show Solution
def merge_dicts(d1, d2):
    # Start with a copy of d1 to avoid modifying the original
    result = d1.copy()
    
    for key, value in d2.items():
        # .get(key, 0) returns 0 if the key doesn't exist yet
        result[key] = result.get(key, 0) + value
    
    return result

dict_a = {'a': 10, 'b': 20}
dict_b = {'b': 5, 'c': 15}

merged = merge_dicts(dict_a, dict_b)
print(f"Merged Dictionary: {merged}")Code language: Python (python)

Explanation of Solution:

  • d1.copy(): It is best practice not to mutate input arguments. Copying ensures the original dict_a remains unchanged.
  • d2.items(): This allows us to loop through both keys and values of the second dictionary simultaneously.
  • result.get(key, 0): This is the “safe” way to access a dictionary. If the key exists, it returns the value; otherwise, it returns 0, allowing for a clean mathematical addition without an if/else block.

Exercise 3: Frequency Map with Counter

Practice Problem: Create a function that takes a string and returns a count of how many times each character appears. Ignore spaces and make it case-insensitive.

Exercise Purpose: While you could build a frequency map with a standard loop, Python’s collections module offers a specialized tool called Counter. This exercise teaches you to leverage the Standard Library to write less code while increasing performance.

Given Input: text = "Python Programming"

Expected Output:

Character Frequency: 
Counter({'p': 2, 'y': 1, 't': 1, 'h': 1, 'o': 2, 'n': 2, 'r': 2, 'a': 1, 'g': 2, 'm': 2, 'i': 1})
+ Hint

Use .lower() and .replace(" ", "") to clean the string before passing it to the Counter class.

+ Show Solution
from collections import Counter

def get_frequency(input_string):
    # Clean the string: remove spaces and lowercase everything
    clean_text = input_string.lower().replace(" ", "")
    
    # Counter automatically builds the frequency map
    return Counter(clean_text)

text = "Python Programming"
freq = get_frequency(text)

print(f"Original: {text}")
print(f"Character Frequency: {freq}")Code language: Python (python)

Explanation of Solution:

  • from collections import Counter: Counter is a subclass of dictionary specifically designed for counting hashable objects.
  • input_string.lower().replace(" ", ""): This pre-processing step ensures that “P” and “p” are counted as the same character and that whitespace doesn’t clutter the results.
  • Counter(clean_text): The constructor takes an iterable (like a string or list) and instantly tallies the occurrences of every element.

Exercise 4: Anagram Checker

Practice Problem: Write a function that determines if two strings are anagrams (contain the exact same characters in a different order).

Exercise Purpose: This problem introduces the concept of algorithmic sorting as a comparison tool. It demonstrates that transforming data into a “canonical” or “standard” form (sorted) makes comparison trivial.

Given Input: word1 = "listen", word2 = "silent"

Expected Output: Is "listen" an anagram of "silent"? True

+ Hint
  • If two strings are anagrams, their sorted versions will be identical strings.
  • Sort both strings in alphabetical order.
+ Show Solution
def is_anagram(str1, str2):
    # Clean and sort both strings
    s1 = sorted(str1.lower().replace(" ", ""))
    s2 = sorted(str2.lower().replace(" ", ""))
    
    # If the sorted lists are identical, they are anagrams
    return s1 == s2

w1, w2 = "listen", "silent"
result = is_anagram(w1, w2)

print(f'Is "{w1}" an anagram of "{w2}"? {result}')Code language: Python (python)

Explanation of Solution:

  • sorted(str1...): The sorted() function takes the characters of the string and returns them as a sorted list (e.g., ['e', 'i', 'l', 'n', 's', 't']).
  • s1 == s2: In Python, you can compare two lists directly using ==. It checks if they have the same elements in the same order.
  • Efficiency: Sorting takes O(n log n) time, which is very efficient for standard string comparisons.

Exercise 5: Flatten a Nested List

Practice Problem: Write a recursive function that takes a list containing other lists (of any depth) and returns a single “flat” list of all elements.

Exercise Purpose: Intermediate Python often involves dealing with nested data (like JSON). This exercise teaches Recursion—the ability of a function to call itself—to drill down into nested structures until it finds base values.

Given Input: nested = [1, [2, 3], [4, [5, 6]], 7]

Expected Output: Flattened: [1, 2, 3, 4, 5, 6, 7]

+ Hint
  • Iterate through the list.
  • If an item is a list, call the function again on that item.
  • If it’s not, append it to your results.
+ Show Solution
def flatten(lst):
    flat_list = []
    
    for item in lst:
        if isinstance(item, list):
            # Recursion: if it's a list, flatten it and extend our results
            flat_list.extend(flatten(item))
        else:
            # Base case: it's a single value, just add it
            flat_list.append(item)
            
    return flat_list

nested_data = [1, [2, 3], [4, [5, 6]], 7]
result = flatten(nested_data)

print(f"Original:  {nested_data}")
print(f"Flattened: {result}")Code language: Python (python)

Explanation of Solution:

  • isinstance(item, list): This checks if the current element is another container.
  • flatten(item): This is the recursive call. The function pauses its current work to “dive” into the sub-list.
  • extend() vs append(): append adds an item as it is, while extend takes all elements from an iterable and adds them individually to the list. This is crucial for merging the results of the recursive calls.

Exercise 6: Reverse Each Word of a String

Practice Problem: Given a sentence, reverse each individual word within the string while maintaining the original word order.

Exercise Purpose: This exercise teaches you the difference between reversing a sequence (the whole string) and iterating through sub-sequences (words). It emphasizes the use of the .split() and .join() methods, which are essential for text processing.

Given Input: "Python is awesome"

Expected Output: "nohtyP si emosewa"

+ Hint
  • Split the string into a list of words.
  • Use slicing [::-1] to reverse each word, and then join them back together with spaces.
+ Show Solution
def reverse_individual_words(sentence):
    # Split sentence into words
    words = sentence.split()
    
    # Reverse each word using slicing
    reversed_words = [word[::-1] for word in words]
    
    # Join them back into a single string
    return " ".join(reversed_words)

# Usage
text = "Python is awesome"
result = reverse_individual_words(text)
print(f"Original: {text}")
print(f"Result:   {result}")Code language: Python (python)

Explanation of Solution:

  • sentence.split(): This breaks the string into a list: ['Python', 'is', 'awesome'].
  • word[::-1]: This is the Pythonic way to reverse a string. It starts at the end and steps backward to the beginning.
  • " ".join(...): This takes the list of reversed words and stitches them together with a space character between each.

Exercise 7: Palindrome Sentence

Practice Problem: Write a function to check if a full sentence is a palindrome. You must ignore case, spaces, and all punctuation marks.

Exercise Purpose: Real-world palindromes often include punctuation (e.g., “Madam, I’m Adam”). This exercise teaches you how to sanitize data using isalnum() (is alphanumeric) before performing logic, ensuring your algorithm only focuses on the relevant characters.

Given Input: "A man, a plan, a canal: Panama"

Expected Output: True

+ Hint

Use a generator expression or loop to build a new string containing only lowercase letters and numbers, then compare that string to its reverse.

+ Show Solution
def is_palindrome_sentence(sentence):
    # Filter out punctuation and spaces, convert to lowercase
    clean_chars = [char.lower() for char in sentence if char.isalnum()]
    
    # Join into a string
    clean_str = "".join(clean_chars)
    
    # Compare with its reverse
    return clean_str == clean_str[::-1]

# Usage
test_s = "A man, a plan, a canal: Panama"
print(f"Is palindrome? {is_palindrome_sentence(test_s)}")Code language: Python (python)

Explanation of Solution:

  • char.isalnum(): This method returns True only if the character is a letter or a number, effectively stripping out commas, colons, and spaces.
  • clean_str[::-1]: By comparing the sanitized string to its reversed version, we can instantly determine symmetry.
  • Case Sensitivity: Converting everything to .lower() is vital, as 'A' does not equal 'a' in Python.

Exercise 8: List Comprehension Filtering (Advanced)

Practice Problem: Given a list of strings, use a single list comprehension to extract strings that meet two criteria: they must be longer than 5 characters AND they must start with a vowel (a, e, i, o, u).

Exercise Purpose: This exercise builds “filter stacking” skills. In professional Python development, you often need to perform complex data extraction in a readable, concise way without writing multiple if statements.

Given Input: ["apple", "education", "ice", "ocean", "python", "umbrella"]

Expected Output: ['education', 'umbrella']

+ Hint

Your condition in the list comprehension will use the and operator and should check s[0].lower() in 'aeiou'.

+ Show Solution
words = ["apple", "education", "ice", "ocean", "python", "umbrella"]

# Filter: starts with vowel AND length > 5
vowel_long = [
    w for w in words 
    if len(w) > 5 and w[0].lower() in 'aeiou'
]

print(f"Original: {words}")
print(f"Filtered: {vowel_long}")Code language: Python (python)

Explanation of Solution:

  • w[0].lower() in 'aeiou': This is a very efficient way to check for multiple possible characters. It checks the first letter of the word against the vowels string.
  • Short-circuiting: Using and is efficient; if len(w) > 5 is False, Python won’t even bother checking if it starts with a vowel.
  • Readability: Even with two conditions, the list comprehension remains clear and declarative.

Exercise 9: Remove Duplicates (Preserving Order)

Practice Problem: Write a function that removes duplicate elements from a list. You cannot use set() because sets do not maintain the original order of elements.

Exercise Purpose: While list(set(items)) is the fastest way to get unique items, it scrambles the order. This exercise teaches you how to use an auxiliary “seen” collection to maintain sequence integrity, a common requirement in data logging.

Given Input: [1, 2, 2, 3, 1, 4, 2]

Expected Output: [1, 2, 3, 4]

+ Hint

Create an empty list for results and an empty set for “seen” items (for O(1) lookup speed). Loop through the input; if an item isn’t in “seen”, add it to both.

+ Show Solution
def remove_duplicates_ordered(items):
    seen = set()
    result = []
    
    for x in items:
        if x not in seen:
            result.append(x)
            seen.add(x)
            
    return result

# Usage
nums = [1, 2, 2, 3, 1, 4, 2]
print(f"Cleaned List: {remove_duplicates_ordered(nums)}")Code language: Python (python)

Explanation of Solution:

  • Hybrid Approach: We use a result list to keep the order and a seen set to make the “is this a duplicate?” check instant.
  • Efficiency: Using if x not in result would work but would get slower as the list grows (O(n2)). Using a set makes this run in O(n) time.
  • Persistence: The first time an element appears, it is locked into its position in the result list and marked in seen.

Exercise 10: Circular Shift (Rotation)

Practice Problem: Create a function rotate_list(lst, n, direction) that shifts the elements of a list by N positions. The direction can be ‘left’ or ‘right’.

Exercise Purpose: List rotation is common in cryptography and UI carousels. This exercise teaches you how to use slicing and the modulo operator to handle shifts that are larger than the list length.

Given Input: List: [1, 2, 3, 4, 5], Shift: 2, Direction: 'right'

Expected Output: [4, 5, 1, 2, 3]

+ Hint

Use slicing. For a right shift of n, the result is lst[-n:] + lst[:-n].

+ Show Solution
def rotate_list(lst, n, direction='right'):
    if not lst:
        return lst
        
    # Handle shifts larger than the list length
    n = n % len(lst)
    
    if direction == 'right':
        # Take the last n elements and put them at the front
        return lst[-n:] + lst[:-n]
    else:
        # Take the first n elements and put them at the back
        return lst[n:] + lst[:n]

# Usage
data = [1, 2, 3, 4, 5]
print(f"Right Shift 2: {rotate_list(data, 2, 'right')}")
print(f"Left Shift 1:  {rotate_list(data, 1, 'left')}")Code language: Python (python)

Explanation of Solution:

  • n = n % len(lst): This ensures that if you shift a 5-item list by 102, it acts like a shift of 2, preventing index errors.
  • Slicing Logic: lst[-n:] captures the “tail” of the list that will wrap around to the front. lst[:-n] captures the “head” that gets pushed forward.
  • + Operator: Python allows you to concatenate lists easily, making the “wrap around” effect look like simple addition.

Exercise 11: Dictionary Merging (Value Grouping)

Practice Problem: Merge two dictionaries. If they share a key, the new dictionary should store a list containing values from both dictionaries instead of overwriting the first one.

Exercise Purpose: Standard dictionary merging (dict.update()) overwrites data. This exercise teaches you how to “preserve” data during a merge, which is essential when combining user settings or merging search results from different sources.

Given Input: d1 = {"a": 1, "b": 2}, d2 = {"b": 3, "c": 4}

Expected Output: {'a': [1], 'b': [2, 3], 'c': [4]}

+ Hint

Iterate through the unique keys of both dictionaries (using a set of keys) and check if the key exists in one or both.

+ Show Solution
def merge_and_group(dict1, dict2):
    combined = {}
    # Get all unique keys from both dictionaries
    all_keys = set(dict1.keys()) | set(dict2.keys())
    
    for key in all_keys:
        values = []
        if key in dict1:
            values.append(dict1[key])
        if key in dict2:
            values.append(dict2[key])
        combined[key] = values
        
    return combined

# Usage
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}
print(f"Grouped Merge: {merge_and_group(d1, d2)}")Code language: Python (python)

Explanation of Solution:

  • set(d1) | set(d2): The pipe | operator performs a Union of the keys, ensuring we don’t miss any keys and we don’t process duplicates.
  • List Values: By making every value a list (even if there is only one item), we maintain a consistent data structure that is easy to iterate over later.

Exercise 12: Inverted Index

Practice Problem: Create a function that “inverts” a dictionary. Convert a dictionary of Author: [List of Books] into a dictionary of Book: Author.

Exercise Purpose: This is the logic behind how search engines work! An Inverted Index allows you to search for a term (the book) and immediately find where it belongs (the author). It emphasizes the use of nested loops and dictionary assignment.

Given Input:

{"Orwell": ["1984", "Animal Farm"], "Huxley": ["Brave New World"]}Code language: Python (python)

Expected Output:

{'1984': 'Orwell', 'Animal Farm': 'Orwell', 'Brave New World': 'Huxley'}
+ Hint
  • Loop through the items of the original dictionary.
  • For every book in the author’s list, make the book a key in the new dictionary and assign the author as the value.
+ Show Solution
def invert_bibliography(data):
    inverted = {}
    for author, books in data.items():
        for book in books:
            inverted[book] = author
    return inverted

# Usage
authors = {
    "Orwell": ["1984", "Animal Farm"],
    "Huxley": ["Brave New World"]
}
print(f"Inverted Index: {invert_bibliography(authors)}")Code language: Python (python)

Explanation of Solution:

  • Nested Iteration: The outer loop picks an author; the inner loop “unpacks” their list of books.
  • Key Swap: We take what was a value (the book) and make it a key. This only works if books are unique across the dataset.
  • Application: This logic is used to map tags to articles, ingredients to recipes, or users to groups.

Exercise 13: Dictionary Sorting (Lambda)

Practice Problem: Given a list of dictionaries (representing employees), sort them based on their “salary” in descending order using a lambda function.

Exercise Purpose: In data processing, you rarely sort simple lists of numbers. You almost always sort “objects” (dictionaries). This exercise teaches you how to use the key parameter in Python’s sort() to target specific fields within a complex structure.

Given Input:

employees = [{"name": "A", "salary": 50}, {"name": "B", "salary": 70}, {"name": "C", "salary": 60}]Code language: Python (python)

Expected Output:

[{'name': 'B', 'salary': 70}, {'name': 'C', 'salary': 60}, {'name': 'A', 'salary': 50}]
+ Hint

Use sorted(list, key=lambda x: x['key_name'], reverse=True).

+ Show Solution
employees = [
    {"name": "Alice", "salary": 50000},
    {"name": "Bob", "salary": 70000},
    {"name": "Charlie", "salary": 60000}
]

# Sort by salary, highest first
sorted_staff = sorted(employees, key=lambda x: x['salary'], reverse=True)

for emp in sorted_staff:
    print(emp)Code language: Python (python)

Explanation of Solution:

  • Lambda as a Proxy: The lambda x: x['salary'] tells Python: “When you compare two dictionaries, don’t look at the whole thing; just look at the ‘salary’ value.”
  • reverse=True: This flag switches the sort from ascending (default) to descending.
  • Stability: Python’s sorted() is stable, meaning if two employees had the same salary, their original order relative to each other would not change.

Exercise 14: Subset and Superset Validation

Practice Problem: Write a script that takes two lists of integers from a user, converts them to sets, and determines if the first set is a Subset, a Superset, or Disjoint from the second.

Exercise Purpose: This exercise introduces formal relational logic between collections. Understanding these relationships is vital when managing user permissions (is this set of permissions a subset of the required ones?) or validating categories.

Given Input: Set A: {1, 2, 3}, Set B: {1, 2, 3, 4, 5}

Expected Output: Set A is a subset of Set B.

+ Hint

Use the built-in methods .issubset(), .issuperset(), and .isdisjoint().

+ Show Solution
def validate_relationships(list1, list2):
    a = set(list1)
    b = set(list2)
    
    if a.issubset(b):
        print("Set A is a subset of Set B.")
    elif a.issuperset(b):
        print("Set A is a superset of Set B.")
    
    if a.isdisjoint(b):
        print("The sets are disjoint (they share no elements).")
    else:
        print(f"The sets share these elements: {a & b}")

# Usage
validate_relationships([1, 2], [1, 2, 3, 4])Code language: Python (python)

Explanation of Solution:

  • .issubset(): Returns True if every element of Set A is contained within Set B.
  • .issuperset(): Returns True if Set A contains every element of Set B (plus potentially more).
  • .isdisjoint(): Returns True if the intersection of the two sets is empty. It’s a quick way to check for “zero overlap.”

Exercise 15: Set Symmetric Difference

Practice Problem: Given two lists of student IDs, find the IDs that appear in either the first or the second list, but not in both.

Exercise Purpose: This is known as the Symmetric Difference. It is a powerful way to identify “exclusive” data. For example, finding customers who visited either in January or February, but did not visit in both months.

Given Input: list1 = [101, 102, 103], list2 = [103, 104, 105]

Expected Output: {101, 102, 104, 105}

+ Hint

Use the ^ operator between two sets.

+ Show Solution
def get_exclusive_ids(ids1, ids2):
    set1 = set(ids1)
    set2 = set(ids2)
    
    # Symmetric difference
    return set1 ^ set2

# Usage
january_visitors = [10, 20, 30, 40]
february_visitors = [30, 40, 50, 60]

exclusive = get_exclusive_ids(january_visitors, february_visitors)
print(f"Visitors exclusive to one month: {exclusive}")Code language: Python (python)

Explanation of Solution:

  • The ^ Operator: This finds elements that are in (Set A OR Set B) minus (Set A AND Set B).
  • Readability: Using the set operator is significantly cleaner than writing two separate loops with if x not in list logic.
  • Performance: Like all set operations in Python, this is executed in O(n) time, making it efficient for thousands of entries.

Exercise 16: Power Set Generation

Practice Problem: Write a function that generates the Power Set of a given set (a set of all possible subsets, including the empty set and the set itself).

Exercise Purpose: Generating power sets is a fundamental concept in Combinatorics and algorithm design (like solving the knapsack problem). This exercise introduces you to the itertools module, specifically combinations.

Given Input: [1, 2, 3]

Expected Output:

[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
+ Hint

Use itertools.combinations in a loop that varies the size of the subset from 0 to the length of the list.

+ Show Solution
from itertools import combinations

def get_power_set(s):
    # Convert input to list to ensure we can index it
    elements = list(s)
    power_set = []
    
    for r in range(len(elements) + 1):
        # Generate all combinations of length r
        for combo in combinations(elements, r):
            power_set.append(combo)
            
    return power_set

# Usage
my_set = {1, 2, 3}
print(f"Power Set: {get_power_set(my_set)}")Code language: Python (python)

Explanation of Solution:

  • combinations(elements, r): This built-in function generates all possible ways to pick r items from the list without repetition and regardless of order.
  • range(len(elements) + 1): We start at 0 to include the Empty Set () and go up to the full length to include the original set.
  • Growth: Keep in mind that the power set of a set with N elements has 2n subsets. For 3 elements, you get 8 subsets; for 10 elements, you get 1,024!

Exercise 17: Age Calculator (Exact)

Practice Problem: Create a function that asks for a user’s birthdate (YYYY-MM-DD) and calculates their exact age today in years, months, and days.

Exercise Purpose: Date math is notoriously difficult because of leap years and varying month lengths. This exercise teaches you to use the datetime and dateutil.relativedelta modules, which are the industry standards for handling temporal data in Python.

Given Input: Birthdate: 1995-05-15, Today: 2026-01-02

Expected Output: Age: 30 years, 7 months, 18 days

+ Hint

Use datetime.strptime to parse the input string and relativedelta to calculate the difference.

+ Show Solution
from datetime import datetime
from dateutil.relativedelta import relativedelta

def calculate_exact_age(birthdate_str):
    try:
        # Parse the input string
        birthdate = datetime.strptime(birthdate_str, "%Y-%m-%d").date()
        today = datetime.now().date()
        
        # Calculate the difference
        diff = relativedelta(today, birthdate)
        
        return f"{diff.years} years, {diff.months} months, {diff.days} days"
    except ValueError:
        return "Invalid date format. Please use YYYY-MM-DD."

# Usage
# Note: You may need to install python-dateutil via pip
user_age = calculate_exact_age("1995-05-15")
print(f"Exact Age: {user_age}")Code language: Python (python)

Explanation of Solution:

  • strptime: This function converts a string into a datetime object based on a format code (%Y for 4-digit year, etc.).
  • relativedelta: Unlike a standard timedelta (which only tracks days and seconds), relativedelta understands the calendar and can correctly calculate years and months while accounting for leap years.
  • Error Handling: The try/except block ensures that if a user types an impossible date (like “1995-02-30”), the program provides a helpful error instead of crashing.

Exercise 18: Countdown Timer (Time Delta)

Practice Problem: Write a function that calculates the exact number of days, hours, and minutes remaining until the upcoming New Year’s Day.

Exercise Purpose: This exercise teaches you how to perform arithmetic on time objects. Since time is not base-10, you cannot simply subtract numbers; you must use datetime objects and understand the “Timedelta” result to extract specific units.

Given Input: Current Date: 2026-01-02

Expected Output: 363 days, 16 hours, 50 minutes until New Year!

+ Hint
  • Calculate the target date (Jan 1 of the next year) and subtract datetime.now().
  • Use integer division and the modulo operator (%) to convert the remaining seconds into hours and minutes.
+ Show Solution
from datetime import datetime

def countdown_to_new_year():
    now = datetime.now()
    # Target: Jan 1 of the next year
    next_year = now.year + 1
    target = datetime(next_year, 1, 1)
    
    diff = target - now
    
    days = diff.days
    # Convert remaining seconds into hours and minutes
    hours, remainder = divmod(diff.seconds, 3600)
    minutes, _ = divmod(remainder, 60)
    
    return f"{days} days, {hours} hours, {minutes} minutes"

print(f"Time remaining: {countdown_to_new_year()} until New Year!")Code language: Python (python)

Explanation of Solution:

  • datetime(next_year, 1, 1): This creates a fixed point in the future to compare against.
  • diff.days: The subtraction of two datetimes returns a timedelta object, which conveniently stores the total number of whole days.
  • divmod(seconds, 3600): This is a Pythonic utility that returns both the quotient (hours) and the remainder (seconds) in one step, making unit conversion much cleaner.

Exercise 19: Business Days Calculator

Practice Problem: Create a function that takes a start date and an integer N, then calculates the date exactly N business days (Monday–Friday) in the future.

Exercise Purpose: In professional settings (finance, shipping, legal), weekends are ignored. This exercise teaches you how to manipulate dates using a loop while applying conditional logic to “skip” specific days of the week.

Given Input: Start: Friday, 2026-01-02, Days to add: 5

Expected Output:

Start: Friday, 2026-01-02
Business Target: Friday, 2026-01-09
+ Hint

Use a while loop that increments the date by one day at a time, but only decrements the “days to add” counter if the day of the week is less than 5 (Monday=0, Friday=4).

+ Show Solution
from datetime import timedelta, datetime

def add_business_days(start_date, n):
    current_date = start_date
    days_added = 0
    
    while days_added < n:
        current_date += timedelta(days=1)
        # weekday() returns 0 for Monday, 5 for Saturday, 6 for Sunday
        if current_date.weekday() < 5:
            days_added += 1
            
    return current_date

# Usage
start = datetime(2026, 1, 2) # A Friday
result = add_business_days(start, 5)
print(f"Start: {start.strftime('%A, %Y-%m-%d')}")
print(f"Business Target: {result.strftime('%A, %Y-%m-%d')}")Code language: Python (python)

Explanation of Solution:

  • current_date.weekday(): This method is the key to identifying weekends.
  • timedelta(days=1): This allows us to “step” through the calendar physically.
  • strftime: We use this to format the output into a human-readable string that includes the name of the day, proving the weekends were skipped.

Exercise 20: Custom Iterator Class

Practice Problem: Create a class PowerOfTwo that allows you to iterate through powers of 2 (1, 2, 4, 8…) up to a specified maximum exponent.

Exercise Purpose: To understand how Python’s for loop works under the hood, you must implement the Iterator Protocol. This requires two magic methods: __iter__ (which returns the iterator object itself) and __next__ (which returns the next value or raises StopIteration).

Given Input: powers = PowerOfTwo(3)

Expected Output: 1 2 4 8

+ Hint
  • Keep a current_exponent state inside the class.
  • Increment it in __next__ until it exceeds the max_exponent.
+ Show Solution
class PowerOfTwo:
    def __init__(self, max_exponent):
        self.max = max_exponent
        self.n = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.n <= self.max:
            result = 2 ** self.n
            self.n += 1
            return result
        else:
            raise StopIteration

# Usage
for val in PowerOfTwo(3):
    print(val)Code language: Python (python)

Explanation of Solution:

  • __iter__: This is called when you start the for loop. It tells Python, “I am an object that can be stepped through.”
  • __next__: This is called on every single iteration of the loop.
  • raise StopIteration: This is the signal Python looks for to know the loop has finished. Without this, the for loop would run forever.
  • Statefulness: Unlike a standard function, the class remembers exactly where it is in the counting process (self.n).

Exercise 21: Find Duplicates in O(n) Time

Practice Problem: Write a function that identifies all duplicate elements in a list. The solution must run in linear time, meaning you should only traverse the list once.

Exercise Purpose: Many beginners use nested loops to find duplicates, resulting in O(n2) complexity (very slow for large data). This exercise teaches you to use a Set as a lookup table. Since checking if an item exists in a set takes O(1) (constant time), the entire operation stays highly efficient.

Given Input: numbers = [1, 2, 3, 2, 4, 5, 1, 6]

Expected Output: Duplicates found: {1, 2}

+ Hint
  • Create an empty set called seen.
  • As you iterate through the list, check if the current number is already in seen. If it is, it’s a duplicate.
+ Show Solution
def find_duplicates(nums):
    seen = set()
    duplicates = set()
    
    for x in nums:
        if x in seen:
            duplicates.add(x)
        else:
            seen.add(x)
            
    return duplicates

numbers = [1, 2, 3, 2, 4, 5, 1, 6]
print(f"Duplicates found: {find_duplicates(numbers)}")Code language: Python (python)

Explanation of Solution:

  • seen = set(): We use a set because it uses a hash table for lookups, making if x in seen nearly instantaneous regardless of the set’s size.
  • The Single Loop: Because we only go through the list once, the time complexity is O(n).
  • duplicates.add(x): We store duplicates in a set as well to ensure that if a number appears three times, it is only reported once in our results.

Exercise 22: Singly Linked List Implementation

Practice Problem: Create a basic Node class and a LinkedList class. Implement a method to append a new node to the end of the list and a method to display the list.

Exercise Purpose: Python handles memory automatically, but understanding Linked Lists is fundamental for technical interviews and understanding how data is structured in memory. This exercise introduces Pointer logic (referencing one object from another).

Given Input: List operations: Append 10, Append 20, Append 30

Expected Output: 10 -> 20 -> 30 -> None

+ Hint
  • Each Node should have data and next. The LinkedList should keep track of the head node.
  • To append, you must start at the head and follow the next pointers until you reach a node where next is None.
+ Show Solution
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None

    def append(self, data):
        new_node = Node(data)
        if not self.head:
            self.head = new_node
            return
        
        last = self.head
        while last.next:
            last = last.next
        last.next = new_node

    def display(self):
        current = self.head
        while current:
            print(f"{current.data} -> ", end="")
            current = current.next
        print("None")

ll = LinkedList()
ll.append(10)
ll.append(20)
ll.append(30)
ll.display()Code language: Python (python)

Explanation of Solution:

  • self.next = None: In Python, None acts like the NULL pointer in C. It signifies the end of the chain.
  • while last.next: This loop “traverses” the list. We keep moving the last pointer until we find the node that doesn’t point to anything yet.
  • Object Referencing: Unlike arrays, which sit in a contiguous block of memory, these nodes can be anywhere; they are only connected because each object holds a reference to the next.

Exercise 23: Stack Implementation (LIFO)

Practice Problem: Implement a Stack data structure using a Python list. Create methods for push (add), pop (remove), and peek (view top element).

Exercise Purpose: Stacks follow the Last-In, First-Out (LIFO) principle. They are used in “Undo” features in software and the “Back” button in browsers. This exercise teaches you how to restrict access to a list to follow a specific architectural pattern.

Given Input: Push: "google.com", "pynative.com" | Action: Pop

Expected Output:

Current Top: pynative.com
Popped: pynative.com
New Top: google.com
+ Hint

In Python, list.append() and list.pop() are highly optimized for adding/removing from the end of the list, making them perfect for stack operations.

+ Show Solution
class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        if not self.is_empty():
            return self.items.pop()
        return "Stack is empty"

    def peek(self):
        return self.items[-1] if not self.is_empty() else None

    def is_empty(self):
        return len(self.items) == 0

browser_history = Stack()
browser_history.push("google.com")
browser_history.push("pynative.com")

print(f"Current Top: {browser_history.peek()}")
print(f"Popped: {browser_history.pop()}")
print(f"New Top: {browser_history.peek()}")Code language: Python (python)

Explanation of Solution:

  • self.items.pop(): Without arguments, pop() removes the last item added, satisfying the LIFO requirement.
  • self.items[-1]: This is a Pythonic way to look at the last item in a list without removing it.
  • Encapsulation: By wrapping the list in a class, we prevent users from accidentally removing items from the middle of the stack.

Exercise 24: Queue Implementation (FIFO)

Practice Problem: Implement a Queue data structure using collections.deque. Create methods for enqueue (add to back) and dequeue (remove from front).

Exercise Purpose: Queues follow First-In, First-Out (FIFO). Using a standard Python list for a queue is slow because removing the first item (list.pop(0)) requires shifting all other items in memory. deque (Double Ended Queue) is designed to make adding/removing from both ends O(1).

Given Input: Enqueue: "Customer A", "Customer B" | Action: Dequeue

Expected Output:

Serving: Customer A
Next in line: Customer B
+ Hint

Use append() for enqueue and popleft() for dequeue.

+ Show Solution
from collections import deque

class Queue:
    def __init__(self):
        self.buffer = deque()

    def enqueue(self, val):
        self.buffer.append(val)

    def dequeue(self):
        if len(self.buffer) == 0:
            return "Queue is empty"
        return self.buffer.popleft()

    def peek(self):
        return self.buffer[0] if self.buffer else None

ticket_line = Queue()
ticket_line.enqueue("Customer A")
ticket_line.enqueue("Customer B")

print(f"Serving: {ticket_line.dequeue()}")
print(f"Next in line: {ticket_line.peek()}")Code language: Python (python)

Explanation of Solution:

  • deque: Short for “double-ended queue,” it is a memory-efficient container that provides fast appends and pops from both ends.
  • popleft(): This is the key method. It removes the very first item added without having to re-index the rest of the collection.
  • Real-world Use: This is how task schedulers and print queues operate.

Exercise 25: Recursive Binary Search

Practice Problem: Implement the Binary Search algorithm recursively to find a target value in a sorted list.

Exercise Purpose: While you saw the iterative version in C, implementing it recursively in Python helps you understand how the Call Stack works. You split the problem into “sub-problems” until you reach a base case.

Given Input: Sorted List: [10, 20, 30, 40, 50, 60], Target: 50

Expected Output: Target found at index: 4

+ Hint
  • Your function needs four parameters: (arr, low, high, x).
  • If low > high, the target isn’t there. Otherwise, calculate mid and call the function again on either the left or right half.
+ Show Solution
def binary_search_recursive(arr, low, high, x):
    # Base Case: target is not in the list
    if high < low:
        return -1

    mid = (low + high) // 2

    # If element is at the middle
    if arr[mid] == x:
        return mid
    
    # If element is smaller than mid, search left sub-array
    elif arr[mid] > x:
        return binary_search_recursive(arr, low, mid - 1, x)
    
    # If element is larger than mid, search right sub-array
    else:
        return binary_search_recursive(arr, mid + 1, high, x)

data = [10, 20, 30, 40, 50, 60]
target = 50
result = binary_search_recursive(data, 0, len(data) - 1, target)

print(f"Target found at index: {result}")Code language: Python (python)

Explanation of Solution:

  • // 2: This is “floor division” in Python. It ensures the middle index is always an integer.
  • The Return Chain: Each recursive call returns its result back up the chain. When the base case (arr[mid] == x) is eventually met, the index value is passed back through all previous function calls to the original caller.
  • Efficiency: Just like the iterative version, this has O(log n) complexity, making it extremely fast for large, sorted datasets.

Exercise 26: Lambda Sorting with Tuples

Practice Problem: Given a list of tuples representing employees (Name, Age, Salary), sort the list primarily by Salary in descending order.

Exercise Purpose: In Python, the sort() method and sorted() function are powerful, but they need help when dealing with complex objects. A lambda function is a small, anonymous “throwaway” function that tells Python exactly which part of the data structure to use as the sorting key.

Given Input:

employees = [("Alice", 30, 50000), ("Bob", 25, 75000), ("Charlie", 35, 60000)]Code language: Python (python)

Expected Output:

[('Bob', 25, 75000), ('Charlie', 35, 60000), ('Alice', 30, 50000)]
+ Hint

Use the key parameter in the sorted() function. The lambda should return the element at index 2 of the tuple.

+ Show Solution
employees = [
    ("Alice", 30, 50000),
    ("Bob", 25, 75000),
    ("Charlie", 35, 60000)
]

# Sort by salary (index 2) in descending order
sorted_employees = sorted(employees, key=lambda emp: emp[2], reverse=True)

print("Employees sorted by Salary (High to Low):")
for emp in sorted_employees:
    print(emp)Code language: Python (python)

Explanation of Solution:

  • lambda emp: emp[2]: This is a mini-function that takes an employee tuple and returns the third item (Salary). Python uses these returned values to decide the order.
  • reverse=True: By default, Python sorts in ascending order. This flag flips it to descending.
  • Stability: Python’s sorting algorithm (Timsort) is stable, meaning if two employees had the same salary, their original relative order would be preserved.

Exercise 27: Map and Filter Combination

Practice Problem: Given a list of numbers, use map() and filter() together to create a list of the squares of only the even numbers.

Exercise Purpose: While list comprehensions are often preferred, understanding map and filter is essential for functional programming and working with large data streams. This exercise teaches you how to chain data transformations: first removing unwanted data, then modifying the rest.

Given Input: nums = [1, 2, 3, 4, 5, 6]

Expected Output: [4, 16, 36]

+ Hint

Use filter() to keep numbers where n % 2 == 0, then wrap that inside a map() that calculates n**2.

+ Show Solution
nums = [1, 2, 3, 4, 5, 6]

# 1. Filter even numbers
# 2. Map those numbers to their squares
result = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, nums)))

print(f"Original: {nums}")
print(f"Squares of evens: {result}")Code language: Python (python)

Explanation of Solution:

  • filter(lambda x: x % 2 == 0, nums): This creates an iterator that only yields numbers divisible by 2.
  • map(lambda x: x**2, ...): This takes the output of the filter and squares each value.
  • list(...): Functions like map and filter return “lazy” objects (iterators). They don’t actually do the work until you convert them to a list or loop over them, which saves memory.

Exercise 28: Custom @timer Decorator

Practice Problem: Write a decorator function named @timer that prints how many seconds a function takes to execute. Apply it to a function that simulates a heavy computation using time.sleep().

Exercise Purpose: Decorators are one of Python’s most powerful features. They allow you to “wrap” a function with extra logic (like logging, authentication, or timing) without changing the function’s internal code. This follows the Open-Closed Principle.

Given Input: A function waste_time() that sleeps for 1.5 seconds.

Expected Output: Function 'waste_time' took 1.5023 seconds to run.

+ Hint

A decorator is a function that takes a function as an argument, defines a wrapper inside, and returns that wrapper.

+ Show Solution
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter() # High-precision start
        result = func(*args, **kwargs)   # Run the actual function
        end_time = time.perf_counter()   # High-precision end
        
        duration = end_time - start_time
        print(f"Function '{func.__name__}' took {duration:.4f} seconds.")
        return result
    return wrapper

@timer
def waste_time():
    time.sleep(1.5)

waste_time()Code language: Python (python)

Explanation of Solution:

  • *args, **kwargs: This ensures the decorator can work on any function, regardless of how many arguments it takes.
  • time.perf_counter(): This is preferred over time.time() for measuring duration because it is a monotonic clock (it doesn’t go backward if the system clock updates).
  • @timer: This is “syntactic sugar” for waste_time = timer(waste_time).

Exercise 29: Fibonacci Generator (Memory Efficiency)

Practice Problem: Create a generator function that yields Fibonacci numbers up to N. Instead of returning a full list, it should yield values one by one.

Exercise Purpose: If you need to generate a billion numbers, a list would crash your computer’s RAM. A Generator uses the yield keyword to return one value at a time and “pauses” its execution state. This is the key to handling Big Data in Python.

Given Input: n = 8

Expected Output:

First 8 Fibonacci numbers:
0 1 1 2 3 5 8 13
+ Hint

Use a while loop and the yield keyword. Swap values using the Pythonic a, b = b, a + b.

+ Show Solution
def fibonacci_gen(limit):
    a, b = 0, 1
    count = 0
    while count < limit:
        yield a
        a, b = b, a + b
        count += 1

# Using the generator
fib = fibonacci_gen(8)

print("First 8 Fibonacci numbers:")
for num in fib:
    print(num, end=" ")Code language: Python (python)

Explanation of Solution:

  • yield a: Instead of finishing the function, yield sends a back to the caller and waits. When the loop asks for the next value, the function resumes exactly where it left off.
  • Memory usage: The memory footprint of this generator is constant (O(1)), whether you generate 5 numbers or 5 million.
  • a, b = b, a + b: This is a simultaneous assignment. Python calculates the right side entirely before assigning any values, avoiding the need for a temp variable.

Exercise 30: Custom Context Manager (with statement)

Practice Problem: Write a class that acts as a context manager for handling a database-style connection (Don’t do actual database connection, simply printing “Connected” and “Disconnected”). Use the with statement to ensure the connection is closed even if an error occurs.

Exercise Purpose: Managing resources like files, network sockets, or database connections is risky. If you forget to close them, you get memory leaks. Context managers automate this using the __enter__ and __exit__ magic methods.

Given Input: A block of code inside a with statement that might raise an Exception.

Expected Output:

Connecting to Database... Processing data... 
Error: something went wrong
Closing Database Connection safely.
+ Hint

The __enter__ method sets up the resource, and __exit__ is guaranteed to run at the end of the with block, even if an exception was raised.

+ Show Solution
class DatabaseConnection:
    def __enter__(self):
        print("Connecting to Database...")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Closing Database Connection safely.")
        # Returning False allows any exception to propagate normally
        return False

# Usage
try:
    with DatabaseConnection() as db:
        print("Processing data...")
        raise Exception("something went wrong")
except Exception as e:
    print(f"Error: {e}")Code language: Python (python)

Explanation of Solution:

  • __enter__: This runs the moment the with block starts.
  • __exit__: This is the “cleanup” phase. It receives information about any errors that happened inside the block (exc_type, etc.).
  • Safety: Notice that even though we raised an Exception, “Closing Database Connection safely” still printed. This is why we use with for file handling (with open(...))—it prevents data corruption.

Exercise 31: The Versatile *args and **kwargs

Practice Problem: Create a “Logger” function that accepts a mandatory message string, followed by an unknown number of positional arguments (*args), and an unknown number of keyword arguments (**kwargs). The function should print the message, then list the extra arguments and metadata.

Exercise Purpose: Intermediate Pythonistas must write functions that are flexible. *args (tuples) and **kwargs (dictionaries) allow your functions to handle dynamic inputs. This is how major libraries like Django or Flask allow for highly customizable function calls.

Given Input:

log_event("User Login", "admin", "dashboard", timestamp="10:00 AM", status="Success")Code language: Python (python)

Expected Output:

Event: User Login
Details: ('admin', 'dashboard')
Metadata: {'timestamp': '10:00 AM', 'status': 'Success'}
+ Hint

Inside the function, args behaves like a tuple and kwargs behaves like a dictionary.

+ Show Solution
def log_event(message, *args, **kwargs):
    print(f"Event: {message}")
    
    if args:
        print(f"Details: {args}")
    
    if kwargs:
        print(f"Metadata: {kwargs}")

# Usage
log_event("User Login", "admin", "dashboard", timestamp="10:00 AM", status="Success")Code language: Python (python)

Explanation of Solution:

  • *args: The asterisk unpacks any extra positional arguments into a tuple. This is useful when you don’t know how many items a user will pass.
  • **kwargs: The double asterisk unpacks named arguments into a dictionary. This is perfect for optional configuration settings.
  • Order Matters: In a function definition, parameters must follow this order: standard arguments, *args, then **kwargs.

Exercise 32: Zip and Enumerate Mapping

Practice Problem: You have two separate lists: one of student names and one of their exam scores. Use zip() to combine them into a dictionary, then use enumerate() to print a ranked list (1st, 2nd, 3rd…).

Exercise Purpose: Data often arrives in separate “columns” (lists). zip() is the standard Pythonic way to merge these columns. enumerate() is a cleaner alternative to using range(len(list)) when you need both the index and the value of an item.

Given Input:

names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]Code language: Python (python)

Expected Output:

Rank 1: Bob scored 92
Rank 2: Alice scored 85
Rank 3: Charlie scored 78
+ Hint

First, create a dictionary. Then, sort the dictionary items by value in descending order before enumerating.

+ Show Solution
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]

# Merge lists into a dictionary
score_map = dict(zip(names, scores))

# Sort by score (value) descending
ranked_scores = sorted(score_map.items(), key=lambda item: item[1], reverse=True)

# Enumerate for ranking
for i, (name, score) in enumerate(ranked_scores, start=1):
    print(f"Rank {i}: {name} scored {score}")Code language: Python (python)

Explanation of Solution:

  • zip(names, scores): This creates pairs like ('Alice', 85). Passing this to dict() creates a lookup table instantly.
  • enumerate(..., start=1): By default, enumerate starts at 0. Setting start=1 makes the output user-friendly for rankings.
  • Unpacking in Loop: The syntax i, (name, score) allows us to unpack the index and the tuple returned by items() in a single step.

Exercise 33: Memoization with lru_cache

Practice Problem: Write a recursive function fibonacci(n) to calculate the nth number in the Fibonacci sequence. Use functools.lru_cache to optimize it so that it doesn’t recalculate the same values multiple times.

Exercise Purpose: Recursive functions for Fibonacci are notoriously slow (O(2n)) because they recalculate the same branches of the “recursion tree” over and over. Memoization stores the results of expensive function calls. Python’s lru_cache (Least Recently Used) is a built-in decorator that handles this automatically.

Note: Without cache, this would take minutes; with cache, it’s instant.

Given Input: fibonacci(50)

Expected Output:

Fibonacci(50) = 12586269025
+ Hint

Import lru_cache from functools and place @lru_cache(maxsize=None) above your function.

+ Show Solution
from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Calculation
n = 50
print(f"Fibonacci({n}) = {fibonacci(n)}")
print(f"Cache Info: {fibonacci.cache_info()}")Code language: Python (python)

Explanation of Solution:

  • @lru_cache: When fibonacci(5) is called, the result is saved in a hidden dictionary. The next time the code needs fibonacci(5), it simply grabs the result from memory.
  • maxsize=None: This tells Python the cache can grow as large as needed.
  • cache_info(): This allows you to see how many “hits” (reused values) and “misses” (newly calculated values) occurred, proving the efficiency.

Exercise 34: Set Operations for Data Analysis

Exercise Purpose: Sets are not just for removing duplicates; they are powerful mathematical tools for comparing collections. Using set logic is much faster and more readable than writing multiple for loops with if conditions.

Given Input:

trial = [1, 2, 3, 4, 5]
paid = [4, 5, 6, 7, 8]Code language: Python (python)

Expected Output:

Upgraded (Both): {4, 5}
Leads (Trial only): {1, 2, 3}
Unique Status (Not both): {1, 2, 3, 6, 7, 8}
+ Hint

Use the operators & (intersection), - (difference), and ^ (symmetric difference).

+ Show Solution
trial = {1, 2, 3, 4, 5} # Converted to sets
paid = {4, 5, 6, 7, 8}

upgraded = trial & paid
leads = trial - paid
unique = trial ^ paid

print(f"Upgraded (Both): {upgraded}")
print(f"Leads (Trial only): {leads}")
print(f"Unique Status (Not both): {unique}")Code language: Python (python)

Explanation of Solution:

  • & (Intersection): Returns only elements present in both sets.
  • - (Difference): Returns elements in the first set that are not in the second.
  • ^ (Symmetric Difference): Returns elements that are in one of the sets, but not both.
  • Performance: These operations are highly optimized at the C level in Python, making them the most efficient way to compare large datasets.

Exercise 35: Inheritance and Method Overriding

Practice Problem: Create a base class Vehicle with an attribute called brand and a method fuel_type(). Then, create a subclass ElectricCar that inherits from Vehicle and overrides the fuel_type() method to return “Electricity.”

Exercise Purpose: Inheritance allows you to define a general behavior in a “parent” class and then specialize it in a “child” class. This prevents code duplication (DRY—Don’t Repeat Yourself) and creates a logical hierarchy in your software.

Given Input: car = ElectricCar("Tesla")

Expected Output:

Brand: Tesla
Fuel Type: Electricity
+ Hint

Use the super().__init__(brand) call in the child class to ensure the parent class is properly initialized.

+ Show Solution
class Vehicle:
    def __init__(self, brand):
        self.brand = brand

    def fuel_type(self):
        return "Petrol/Diesel"

class ElectricCar(Vehicle):
    # Override the fuel_type method
    def fuel_type(self):
        return "Electricity"

# Usage
my_tesla = ElectricCar("Tesla")
print(f"Brand: {my_tesla.brand}")
print(f"Fuel Type: {my_tesla.fuel_type()}")Code language: Python (python)

Explanation of Solution:

  • class ElectricCar(Vehicle): This syntax tells Python that ElectricCar is a specialized version of Vehicle.
  • Method Overriding: By defining fuel_type() again in the child class, we replace the parent’s logic with something specific to electric cars.
  • Inheritance of Attributes: Notice we didn’t define self.brand inside ElectricCar; it was automatically inherited from the Vehicle class.

Exercise 36: Encapsulation (Private Attributes)

Practice Problem: Create a BankAccount class where the balance is a private attribute (cannot be accessed directly from outside the class). Provide deposit and withdraw methods to modify the balance safely.

Exercise Purpose: Encapsulation is about data protection. By making attributes private, you prevent external code from accidentally setting a balance to a negative number or a string. You force all changes to go through “gatekeeper” methods.

Given Input:

account = BankAccount(100)
account.deposit(50)
account.withdraw(200) # This should trigger a warningCode language: Python (python)

Expected Output:

Deposited 50.
New Balance: 150
Insufficient funds! Final Balance: 150
+ Hint

In Python, prefixing an attribute with two underscores (e.g., self.__balance) makes it “private” through name mangling.

+ Show Solution
class BankAccount:
    def __init__(self, initial_balance):
        self.__balance = initial_balance # Private attribute

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print(f"Deposited {amount}. New Balance: {self.__balance}")

    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            print(f"Withdrew {amount}. New Balance: {self.__balance}")
        else:
            print("Insufficient funds!")

    def get_balance(self):
        return self.__balance

# Usage
acc = BankAccount(100)
acc.deposit(50)
acc.withdraw(200)
print(f"Final Balance: {acc.get_balance()}")Code language: Python (python)

Explanation of Solution:

  • self.__balance: The double underscore tells Python to hide this variable. If you try to run print(acc.__balance) outside the class, it will raise an AttributeError.
  • Validation: The withdraw method includes an if statement to check if the user has enough money. This is the “gatekeeper” logic that encapsulation enables.
  • Getter Method: Since the attribute is private, we provide get_balance() to let users see the data without being able to corrupt it.

Exercise 37: Property Decorators (@property)

Practice Problem: Create a Student class with a name and a score. Use the @property decorator to create a getter for the score, and a @score.setter to ensure that any score assigned is between 0 and 100.

Exercise Purpose: Property decorators allow you to treat a method like an attribute. This is “Pythonic” because it allows you to add validation logic (like checking the 0–100 range) later in development without changing the way users interact with your class.

Given Input:

s = Student("Kevin")
s.score = 105 # Should trigger a ValueErrorCode language: Python (python)

Expected Output: ValueError: Score must be between 0 and 100.

+ Hint

The actual data should be stored in a “protected” variable like self._score (single underscore).

+ Show Solution
class Student:
    def __init__(self, name):
        self.name = name
        self._score = 0 # Internal storage

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if 0 <= value <= 100:
            self._score = value
        else:
            raise ValueError("Score must be between 0 and 100.")

# Usage
s = Student("Kevin")
try:
    s.score = 95  # Looks like attribute assignment, but calls the setter
    print(f"{s.name}'s score: {s.score}")
    s.score = 105 # This will fail
except ValueError as e:
    print(f"Error: {e}")Code language: Python (python)

Explanation of Solution:

  • @property: This turns the score() method into a “getter.” You access it via s.score instead of s.score().
  • @score.setter: This method is triggered when you try to use the = operator. It allows for “invisible” data validation.
  • Refactoring: This is extremely useful for maintaining backward compatibility in large codebases.

Exercise 38: Class Methods vs. Static Methods

Practice Problem: Create a class Pizza that has a price attribute. Implement a @classmethod called margherita() that returns a pre-configured Pizza object and a @staticmethod called validate_topping() that checks if a topping is “healthy.”

Exercise Purpose: 1. Class Methods are “Factory” methods; they have access to the class (cls) and are used to create specific instances. 2. Static Methods are “Utility” methods; they live inside the class for organization but don’t need access to class or instance data.

Given Input:

my_pizza = Pizza.margherita()
is_valid = Pizza.validate_topping("pineapple")Code language: Python (python)

Expected Output:

Pizza ordered: Margherita
Is topping valid? True
+ Hint

Use @classmethod with cls as the first argument, and @staticmethod with no special first argument.

+ Show Solution
class Pizza:
    def __init__(self, name, toppings):
        self.name = name
        self.toppings = toppings

    @classmethod
    def margherita(cls):
        # Returns a new instance of the class (factory pattern)
        return cls("Margherita", ["cheese", "tomato"])

    @staticmethod
    def validate_topping(topping):
        # Independent utility logic
        prohibited = ["plastic", "metal"]
        return topping not in prohibited

# Usage
order = Pizza.margherita()
print(f"Pizza ordered: {order.name}")
print(f"Is 'mushrooms' valid? {Pizza.validate_topping('mushrooms')}")Code language: Python (python)

Explanation of Solution:

  • cls(...): Inside a class method, cls refers to the Pizza class itself. Calling cls() is the same as calling Pizza().
  • Organization: Static methods help keep related logic inside the class rather than floating around as global functions.
  • Factory Pattern: Class methods are great for providing “presets” (like different types of pizzas or different ways to load data).

Exercise 39: Magic Methods (__str__ and __add__)

Practice Problem: Create a Point class that represents (x, y) coordinates. Implement __str__ so that printing the object looks like (x, y) and implement __add__ so that p1 + p2 adds the coordinates together.

Exercise Purpose: “Magic methods” (or Dunder methods) allow your custom objects to “hook into” Python’s built-in syntax. By implementing __add__, you tell Python what the + sign should do when it encounters your objects.

Given Input:

p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2)Code language: Python (python)

Expected Output: (4, 6)

+ Hint

__add__ should return a new instance of the Point class.

+ Show Solution
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # Controls how the object is printed
    def __str__(self):
        return f"({self.x}, {self.y})"

    # Overloads the '+' operator
    def __add__(self, other):
        new_x = self.x + other.x
        new_y = self.y + other.y
        return Point(new_x, new_y)

# Usage
p1 = Point(1, 2)
p2 = Point(3, 4)
combined = p1 + p2

print(f"Point 1: {p1}")
print(f"Point 2: {p2}")
print(f"Combined Result: {combined}")Code language: Python (python)

Explanation of Solution:

  • __str__: Without this, printing the object would result in a messy memory address like <__main__.Point object at 0x... >.
  • self and other: In __add__, self is the object on the left of the + and other is the object on the right.
  • Operator Overloading: This makes your custom objects feel like “first-class citizens” in Python, just like integers or strings.

Exercise 40: Abstract Base Classes (ABC)

Practice Problem: Create an abstract base class Shape with an abstract method area(). Then, implement two subclasses, Circle and Square, that provide the actual logic for calculating their respective areas.

Exercise Purpose: Abstract Base Classes (ABCs) act as a contract. They define a set of methods that all subclasses must implement. If you try to instantiate a subclass that hasn’t defined area(), Python will raise an error. This is vital for building large frameworks where you want to ensure consistency across many different modules.

Given Input: shapes = [Circle(5), Square(4)]

Expected Output:

Circle Area: 78.54 Square Area: 16
+ Hint

Use the abc module and the @abstractmethod decorator.

+ Show Solution
from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return math.pi * (self.radius ** 2)

class Square(Shape):
    def __init__(self, side):
        self.side = side
        
    def area(self):
        return self.side ** 2

# Usage
shapes = [Circle(5), Square(4)]
for s in shapes:
    print(f"{type(s).__name__} Area: {s.area():.2f}")Code language: Python (python)

Explanation of Solution:

  • ABC: Inheriting from ABC prevents you from creating a generic Shape() object.
  • @abstractmethod: This “marks” the method. Python will now block any subclass from being created unless it overrides this specific method.
  • Polymorphism: In the loop, we call .area() on different objects without needing to know their specific type. This is the heart of flexible software design.

Exercise 41: Multiple Inheritance and MRO

Practice Problem: Create two classes, Flyer (with a fly() method) and Swimmer (with a swim() method). Create a Duck class that inherits from both. Use the __mro__ attribute to inspect the search order.

Exercise Purpose: Python allows a class to inherit from multiple parents. However, this can get complicated (the “Diamond Problem”). This exercise teaches you how Python resolves which method to call using the Method Resolution Order (MRO).

Given Input:

d = Duck()
d.fly()
d.swim()Code language: Python (python)

Expected Output:

Flying high!
Swimming fast!
MRO: (<class '__main__.Duck'>, <class '__main__.Flyer'>, <class '__main__.Swimmer'>, <class 'object'>)
+ Hint

Simply list both parent classes in the parentheses: class Duck(Flyer, Swimmer):

+ Show Solution
class Flyer:
    def fly(self):
        print("Flying high!")

class Swimmer:
    def swim(self):
        print("Swimming fast!")

class Duck(Flyer, Swimmer):
    pass

# Usage
d = Duck()
d.fly()
d.swim()

print(f"MRO: {Duck.__mro__}")Code language: Python (python)

Explanation of Solution:

  • Multiple Inheritance: Duck now possesses all methods from both Flyer and Swimmer.
  • MRO (Method Resolution Order): Python uses the C3 Linearization algorithm to determine the order in which it searches for methods. You can see this by checking ClassName.__mro__.
  • Object: Every class in Python 3 ultimately inherits from the built-in object class, which is why it appears last in the MRO.

Exercise 42: Composition over Inheritance

Practice Problem: Build a Computer class that is “composed” of a CPU object and a RAM object, rather than inheriting from them.

Exercise Purpose: A common mantra in software engineering is “Compose instead of Inherit.” While inheritance implies a “is-a” relationship (a Dog is a Mammal), composition implies a “has-a” relationship (a Computer has a CPU). This makes your code more modular and easier to change later.

Given Input: my_comp = Computer("Intel i7", "16GB")

Expected Output: Computer with Intel i7 CPU and 16GB RAM.

+ Hint

Instantiate the CPU and RAM objects inside the Computer‘s __init__ method.

+ Show Solution
class CPU:
    def __init__(self, model):
        self.model = model

class RAM:
    def __init__(self, size):
        self.size = size

class Computer:
    def __init__(self, cpu_model, ram_size):
        # The Computer "has" a CPU and RAM
        self.cpu = CPU(cpu_model)
        self.ram = RAM(ram_size)

    def __str__(self):
        return f"Computer with {self.cpu.model} CPU and {self.ram.size} RAM."

# Usage
my_pc = Computer("Intel i7", "16GB")
print(my_pc)Code language: Python (python)

Explanation of Solution:

  • Decoupling: The Computer class doesn’t need to know how a CPU works; it just knows it has one.
  • Flexibility: If you wanted to swap the CPU for a different brand later, you only change the internal object, not the entire inheritance tree of the class.

Exercise 43: The Singleton Pattern

Practice Problem: Implement a Database class that ensures only one instance of itself can ever exist. If a user tries to create a second instance, it should return the first one created.

Exercise Purpose: Sometimes, you need exactly one instance of a class to coordinate actions across a system (like a log file manager or a database connection pool). This exercise introduces the __new__ magic method, which controls the actual creation of the object (before __init__ even runs).

Given Input:

db1 = Database()
db2 = Database()
print(db1 is db2)Code language: Python (python)

Expected Output:

Loading Database... True (Both variables point to the same memory address)
+ Hint

Store the instance in a class-level variable. Check if it exists in __new__.

+ Show Solution
class Database:
    _instance = None # Class-level variable to hold the single instance

    def __new__(cls):
        if cls._instance is None:
            print("Loading Database (First time only)...")
            cls._instance = super(Database, cls).__new__(cls)
        return cls._instance

# Usage
db1 = Database()
db2 = Database()

print(f"Are they the same instance? {db1 is db2}")Code language: Python (python)

Explanation of Solution:

  • __new__: This method is the actual “constructor.” It returns the object instance.
  • cls._instance: By checking this variable, we ensure that the call to super().__new__ only happens once.
  • is operator: In Python, is checks for identity (memory address), whereas == checks for equality (value). For a Singleton, is must return True.

Exercise 44: Data Classes (@dataclass)

Practice Problem: Refactor a standard Book class (with title, author, and pages) into a dataclass. Show how it automatically handles __init__ and __repr__.

Exercise Purpose: Writing self.x = x fifty times in a large project is tedious. Python 3.7+ introduced dataclasses to automatically generate “boilerplate” code like __init__, __repr__ (printable representation), and __eq__. It makes your code cleaner and faster to write.

Given Input: b1 = Book("1984", "George Orwell", 328)

Expected Output: Book(title='1984', author='George Orwell', pages=328)

+ Hint

Use from dataclasses import dataclass and simply list the attributes with type hints.

+ Show Solution
from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    pages: int

# Usage
b1 = Book("1984", "George Orwell", 328)
b2 = Book("1984", "George Orwell", 328)

print(b1) # Automatically has a nice __repr__
print(f"Is b1 equal to b2? {b1 == b2}") # Automatically handles comparisonCode language: Python (python)

Explanation of Solution:

  • Type Hints: Dataclasses require type hints (e.g., : str). While Python doesn’t strictly enforce these at runtime, they are necessary for the dataclass generator to work.
  • __repr__: Notice that print(b1) actually shows the data, unlike a standard class which would show a memory address.
  • __eq__: Dataclasses automatically implement equality logic based on the data values, making them perfect for “Data Transfer Objects” (DTOs).

Exercise 45: CSV Processor

Practice Problem: Write a script that reads a CSV file containing employee names and salaries. Calculate the average salary and write the results (Original Data + Average) into a new CSV file.

Exercise Purpose: CSV (Comma-Separated Values) is the most common format for data exchange. This exercise teaches you to use the built-in csv module, specifically DictReader and DictWriter, which allow you to treat rows as dictionaries rather than just lists of strings.

Given Input (data.csv):

Name,Salary
Alice,50000
Bob,60000
Charlie,70000

Expected Output:

output.csv: 
Includes the original data and a final row: Average,60000.0.
+ Hint

Use DictReader to automatically map the header to values. Remember to convert salary strings to integers for math.

+ Show Solution
import csv

def process_salaries(input_file, output_file):
    salaries = []
    rows = []

    # Read the data
    with open(input_file, mode='r', newline='') as f:
        reader = csv.DictReader(f)
        for row in reader:
            salaries.append(int(row['Salary']))
            rows.append(row)

    avg_salary = sum(salaries) / len(salaries)

    # Write the data
    fieldnames = ['Name', 'Salary']
    with open(output_file, mode='w', newline='') as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)
        writer.writerow({'Name': 'Average', 'Salary': avg_salary})

# Note: You would need a 'data.csv' file in your directory to run this.Code language: Python (python)

Explanation of Solution:

  • DictReader: This is safer than basic reader because it lets you access columns by name (row['Salary']) rather than index (row[1]). This makes your code break-resistant if columns are rearranged.
  • newline='': This is a standard Python practice when opening files for the csv module to prevent blank lines from being inserted between rows on different operating systems.
  • Context Managers: We use with open(...) to ensure the file is closed automatically, preventing data corruption.

Exercise 46: JSON Parser and Nested Access

Practice Problem: Given a JSON string representing a user profile with nested settings, parse the string and safely update a specific nested value (e.g., changing the “theme” from “light” to “dark”).

Exercise Purpose: JSON is the language of the web. Most APIs send and receive data in this format. This exercise teaches you how to convert between Python objects (dictionaries/lists) and JSON strings using the json module.

Given Input:

user_json = '{"id": 1, "profile": {"name": "Alice", "settings": {"theme": "light"}}}'Code language: Python (python)

Expected Output:

Updated JSON: {"id": 1, "profile": {"name": "Alice", "settings": {"theme": "dark"}}}
+ Hint

Use json.loads() (load string) to convert to a dictionary, and json.dumps() (dump string) to convert back to JSON format.

+ Show Solution
import json

user_json = '{"id": 1, "profile": {"name": "Alice", "settings": {"theme": "light"}}}'

# Convert string to dictionary
data = json.loads(user_json)

# Access nested keys and update
data['profile']['settings']['theme'] = 'dark'

# Convert back to JSON string with pretty printing
updated_json = json.dumps(data, indent=4)

print("Updated Profile Settings:")
print(updated_json)Code language: Python (python)

Explanation of Solution:

  • json.loads vs json.load: loads (with an ‘s’) is for Strings; load is for file objects.
  • Nested Dictionaries: JSON data is parsed into nested Python dictionaries. You access deep values by “chaining” keys: data[key1][key2].
  • indent=4: This argument in dumps makes the output “pretty” and human-readable rather than one long, cramped line.

Exercise 47: Regular Expressions (Email Extractor)

Practice Problem: Write a script that takes a long block of messy text and extracts all valid email addresses using the re module.

Exercise Purpose: Regular Expressions (Regex) are a language within a language. They are used for advanced pattern matching. Instead of writing complex loops to search for an “@” and a “.”, Regex allows you to define a “template” that describes what an email looks like.

Given Input:

text = "Contact us at support@company.com or sales.dept@office.org for help."Code language: Python (python)

Expected Output: ['support@company.com', 'sales.dept@office.org']

+ Hint

A simple email pattern is [\w\.-]+@[\w\.-]+\.\w+.

+ Show Solution
import re

text = "Contact us at support@company.com or sales.dept@office.org for help."

# Regex pattern for a standard email
email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'

emails = re.findall(email_pattern, text)

print(f"Emails found: {emails}")Code language: Python (python)

Explanation of Solution:

  • r'...': The r stands for “raw string.” It tells Python not to treat backslashes as special characters, which is essential for Regex.
  • re.findall(): This function scans the entire string and returns all matches as a list.
  • [a-zA-Z0-9._%+-]+: This part of the pattern matches the username portion (letters, numbers, and certain symbols like dots).

Exercise 48: Robust Error Handling (Try/Except/Finally)

Practice Problem: Write a function divide_numbers(a, b) that handles ZeroDivisionError and TypeError (if the user passes a string). Use a finally block to print “Operation complete” regardless of the outcome.

Exercise Purpose: Real-world code fails. Users enter text where they should enter numbers, and servers go offline. Professional Pythonistas use try/except to “catch” these errors gracefully so the entire program doesn’t crash.

Given Input:

  1. divide_numbers(10, 2)
  2. divide_numbers(10, 0)
  3. divide_numbers(10, "apple")

Expected Output:

Result: 5.0
Operation complete.
--------------------
Error: Cannot divide by zero!
Operation complete.
--------------------
Error: Please provide numbers (integers or floats).
Operation complete.
+ Hint

You can catch multiple exceptions by using multiple except blocks.

+ Show Solution
def divide_numbers(a, b):
    try:
        result = a / b
        print(f"Result: {result}")
    except ZeroDivisionError:
        print("Error: Cannot divide by zero!")
    except TypeError:
        print("Error: Please provide numbers (integers or floats).")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
    finally:
        print("Operation complete.")

# Tests
divide_numbers(10, 0)
print("-" * 20)
divide_numbers(10, "apple")Code language: Python (python)

Explanation of Solution:

  • Specific Exceptions: Always catch specific errors (like ZeroDivisionError) before catching the general Exception. This helps in debugging.
  • finally: This block is guaranteed to run. It is usually used for cleaning up resources, like closing a database connection or a file.
  • Graceful Failure: Instead of the program stopping abruptly with a “Traceback,” the user gets a helpful message, and the program continues.

Exercise 49: OS Module (File Searcher)

Practice Problem: Write a function that walks through a directory and all of its subdirectories, printing the full path of every .txt file it finds.

Exercise Purpose: Being able to navigate the file system programmatically is a core skill for automation. The os.walk() function is a powerful generator that handles the complex recursion of folders for you.

Given Input: A root directory name.

Expected Output:

Found: /Users/name/Documents/notes.txt 
Found: /Users/name/Documents/Work/todo.txt
+ Hint

os.walk(path) yields three values: the current root, the dirs in that root, and the files in that root.

+ Show Solution
import os

def find_txt_files(start_dir):
    for root, dirs, files in os.walk(start_dir):
        for file in files:
            if file.endswith(".txt"):
                # Join the folder path and file name correctly
                full_path = os.path.join(root, file)
                print(f"Found: {full_path}")

# Usage (Use '.' for current directory)
find_txt_files('.')Code language: Python (python)

Explanation of Solution:

  • os.walk(): This function does the heavy lifting of recursion. It keeps “walking” down into sub-folders until there are none left.
  • os.path.join(): This is critical for cross-platform compatibility. It uses / on Mac/Linux and \ on Windows so your code doesn’t break when shared.
  • Filtering: We use .endswith(".txt") to pick only the files we care about, a common pattern in data processing scripts.

Exercise 50: Multi-threading (Concurrency)

Practice Problem: Use the threading module to run two functions simultaneously. One function should “Download File A” and the other “Download File B” (using time.sleep to simulate the delay).

Exercise Purpose: Python is often “I/O bound,” meaning it spends a lot of time waiting for things (like web downloads). Threading allows your program to do other work while waiting, effectively running tasks in parallel and saving time.

Given Input: Two tasks taking 2 seconds each.

Expected Output:

Total time taken: 2 seconds (instead of 4 seconds).
+ Hint

Create threading.Thread objects and use .start() to begin them and .join() to wait for them to finish.

+ Show Solution
import threading
import time

def download_file(name, duration):
    print(f"Starting download: {name}")
    time.sleep(duration)
    print(f"Finished download: {name}")

start = time.perf_counter()

# Create threads
t1 = threading.Thread(target=download_file, args=("File_A", 2))
t2 = threading.Thread(target=download_file, args=("File_B", 2))

# Start threads
t1.start()
t2.start()

# Wait for both to finish before continuing
t1.join()
t2.join()

end = time.perf_counter()
print(f"Total time taken: {end - start:.2f} seconds")Code language: Python (python)

Explanation of Solution:

  • target and args: We pass the function name to target and its arguments as a tuple to args.
  • start(): This tells the OS to begin executing the function in a separate thread.
  • join(): This is a “blocker.” It tells the main program: “Don’t finish the script until these threads are done.” Without this, the script might end before the downloads finish.

Exercise 51: API Requests with Error Handling

Practice Problem: Use the requests library to fetch data from a public API (like JSONPlaceholder). Handle potential connection errors or timeouts using a try/except block.

Exercise Purpose: Most modern Python applications rely on third-party APIs. Learning to handle the “unreliability” of the internet (timeouts, 404 errors, 500 server errors) is what makes a script “robust.”

Given Input:

URL = "https://jsonplaceholder.typicode.com/todos/1"Code language: Python (python)

Expected Output:

Status Code: 200 Data: {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
+ Hint

Use response.raise_for_status() to automatically raise an error for bad status codes (like 404 or 500).

+ Show Solution
import requests

def get_todo_data(todo_id):
    url = f"https://jsonplaceholder.typicode.com/todos/{todo_id}"
    try:
        response = requests.get(url, timeout=5)
        
        # This raises an error if the status is 4xx or 5xx
        response.raise_for_status()
        
        data = response.json()
        print(f"Data: {data}")
        
    except requests.exceptions.HTTPError as err:
        print(f"HTTP Error: {err}")
    except requests.exceptions.ConnectionError:
        print("Error: Could not connect to the server.")
    except Exception as e:
        print(f"An error occurred: {e}")

get_todo_data(1)Code language: Python (python)

Explanation of Solution:

  • timeout=5: Never make a request without a timeout! If the server is hanging, your program will wait forever unless you set a limit.
  • response.json(): The requests library has a built-in parser that converts JSON strings directly into Python dictionaries.
  • raise_for_status(): This is a clean way to handle errors like “404 Not Found” without writing dozens of if/else statements.

Exercise 52: Word Frequency Counter

Practice Problem: Write a function that takes a paragraph of text and returns a dictionary where the keys are unique words and the values are the number of times those words appear. Ensure the counter is case-insensitive and ignores punctuation.

Exercise Purpose: This combines string cleaning (regex or string methods) with dictionary logic. It’s a foundational task for Natural Language Processing (NLP) and data analysis.

Given Input:

text = "Python is great. Python is fast, and learning Python is fun!"Code language: Python (python)

Expected Output:

{'python': 3, 'is': 3, 'great': 1, 'fast': 1, 'and': 1, 'learning': 1, 'fun': 1}
+ Hint

Use .lower() for case-insensitivity and .replace() or re.sub() to remove commas and periods before splitting the string into a list.

+ Show Solution
import re
from collections import Counter

def count_words(text):
    # Remove punctuation using Regex and convert to lowercase
    clean_text = re.sub(r'[^\w\s]', '', text).lower()
    
    # Split into words and count
    words = clean_text.split()
    return dict(Counter(words))

text = "Python is great. Python is fast, and learning Python is fun!"
print(f"Word Frequency: {count_words(text)}")Code language: Python (python)

Explanation of Solution:

  • re.sub(r'[^\w\s]', '', text): This regular expression removes anything that isn’t a word character or whitespace. It keeps “Python is fast” from becoming “fast,” (with the comma).
  • .split(): By default, this splits by any whitespace, turning the string into a list of individual words.
  • Counter: As seen in earlier exercises, this is the most efficient way to tally items in an iterable.

Exercise 53: Student Management System

Practice Problem: Create a system using a dictionary of dictionaries to manage student records. Implement functions to add_student, update_grade, and display_all. Each student should have a unique ID.

Exercise Purpose: This exercise teaches you how to manage Nested Data Structures. In Python, dictionaries are often used as “in-memory databases” before transitioning to actual SQL or NoSQL databases.

Given Input:

  1. Add ID 101: “Alice”, Grade: “A”
  2. Update ID 101: Grade: “A+”

Expected Output: ID: 101 | Name: Alice | Grade: A+

+ Hint

Use the student ID as the “primary key” (the main dictionary key) and another dictionary as the value to store the name and grade.

+ Show Solution
students = {}

def add_student(s_id, name, grade):
    students[s_id] = {"name": name, "grade": grade}

def update_grade(s_id, new_grade):
    if s_id in students:
        students[s_id]["grade"] = new_grade
    else:
        print("Student not found.")

def display_students():
    for s_id, info in students.items():
        print(f"ID: {s_id} | Name: {info['name']} | Grade: {info['grade']}")

add_student(101, "Alice", "A")
update_grade(101, "A+")
display_students()Code language: Python (python)

Explanation of Solution:

  • students[s_id]: Using the ID as a key allows for O(1) (instant) lookups, which is much faster than searching through a list of students.
  • Nested Access: students[s_id]["grade"] shows how we drill down into the inner dictionary to modify specific data.
  • Membership Testing: if s_id in students prevents the program from crashing if we try to update a non-existent ID.

Exercise 54: Password Strength Checker

Practice Problem: Write a function that takes a string and returns a “Strength Score” (0–5). The password gets 1 point for meeting each criteria: 8+ characters, has uppercase, has lowercase, has a digit, and has a special character (e.g., !, @, #).

Exercise Purpose: This reinforces the use of Boolean Logic and string character testing methods like .isupper(), .isdigit(), and the any() function.

Given Input: "P@ss123"

Expected Output: Strength: 5/5 (Very Strong)

+ Hint

Create a list of boolean checks and use sum() on that list. In Python, True equals 1 and False equals 0.

+ Show Solution
def check_password(password):
    checks = [
        len(password) >= 8,
        any(char.isupper() for char in password),
        any(char.islower() for char in password),
        any(char.isdigit() for char in password),
        any(not char.isalnum() for char in password) # Special character check
    ]
    
    score = sum(checks)
    levels = {5: "Very Strong", 4: "Strong", 3: "Moderate", 2: "Weak", 1: "Very Weak", 0: "Invalid"}
    
    return f"Strength: {score}/5 ({levels.get(score)})"

print(check_password("P@ssword123"))Code language: Python (python)

Explanation of Solution:

  • any(...) with a Generator: any(char.isdigit() for char in password) is a very efficient way to check if at least one character meets the criteria without writing a full loop.
  • not char.isalnum(): This is a clever way to detect special characters. If a character is neither a letter (alpha) nor a number (numeric), it must be a symbol or space.
  • sum(checks): Since True is treated as 1, summing the list of booleans gives us the final score instantly.

Exercise 55: Number Guessing Game (Computer vs User)

Practice Problem: Create a game where the computer picks a random number between 1 and 100. The user has 10 attempts to guess it. After each guess, the computer tells the user if the actual number is “Higher” or “Lower.”

Exercise Purpose: This project focuses on Control Flow (while-loops) and the random module. It also introduces basic User Input Validation to ensure the game doesn’t crash if a user types something that isn’t a number.

Given Input: Target: 42 | User Guess: 50

Expected Output:

I'm thinking of a number between 1 and 100.
(10 left) Enter guess: 45
Higher!
(9 left) Enter guess: 97
Lower!
(8 left) Enter guess: 52
Higher!
(7 left) Enter guess: 85
Lower!
(6 left) Enter guess: 93
Lower!
(5 left) Enter guess: 54
Correct! The number was 54.
+ Hint

Use random.randint(1, 100) and a while loop that continues as long as the guess is wrong and attempts are > 0.

+ Show Solution
import random

def play_game():
    target = random.randint(1, 100)
    attempts = 10
    
    print("I'm thinking of a number between 1 and 100.")

    while attempts > 0:
        try:
            guess = int(input(f"({attempts} left) Enter guess: "))
        except ValueError:
            print("Invalid input. Enter a number!")
            continue

        if guess == target:
            print(f"Correct! The number was {target}.")
            return
        elif guess < target:
            print("Higher!")
        else:
            print("Lower!")
        
        attempts -= 1

    print(f"Game Over. The number was {target}.")

# play_game() # Uncomment to runCode language: Python (python)

Explanation of Solution:

  • random.randint(1, 100): This includes both endpoints (1 and 100) in the possible range.
  • try/except: Wrapping int(input()) is essential because users often enter non-integers by mistake.
  • return inside the loop: This is a clean way to exit the entire function immediately once the correct answer is found.

Exercise 56: File Word Count Tool

Practice Problem: Write a script that reads a text file and prints the total number of lines, words, and characters (including spaces).

Exercise Purpose: This mimics the Unix wc (word count) command. It teaches you how to efficiently process a file line-by-line, which is a key skill for working with large logs or text datasets.

Given Input (sample.txt):

Hello World
Python is fun.

Expected Output: Lines: 2 | Words: 5 | Characters: 27

+ Hint
  • You can iterate over a file object directly to get lines.
  • Use len(line) for characters and len(line.split()) for words.
+ Show Solution
def file_stats(filename):
    lines, words, chars = 0, 0, 0
    
    try:
        with open(filename, 'r') as f:
            for line in f:
                lines += 1
                words += len(line.split())
                chars += len(line)
        
        print(f"Lines: {lines} | Words: {words} | Characters: {chars}")
    except FileNotFoundError:
        print("The file does not exist.")

# file_stats("my_story.txt")Code language: Python (python)

Explanation of Solution:

  • Line-by-Line Iteration: for line in f is memory-efficient because Python doesn’t load the entire file into RAM at once—it only reads one line at a time.
  • len(line): This includes the newline character (\n) at the end of each line, which is standard for “character counts” in most editors.
  • with open(...): Ensuring the file is automatically closed prevents system resource leaks.

Exercise 57: Prime Number Generator

Practice Problem: Write a function that takes a range (start and end) and returns a list of all prime numbers within that range. Optimize the check by only testing divisors up to the square root of the number.

Exercise Purpose: This exercise sharpens your understanding of nested loops and mathematical optimization. Checking divisors up to sqrt{n} significantly improves performance compared to checking every number up to n.

Given Input: Range: 10 to 50

Expected Output:

Primes: [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
+ Hint
  • A number is prime if it is greater than 1 and has no divisors other than 1 and itself.
  • Use num**0.5 to find the square root.
+ Show Solution
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True

def generate_primes(start, end):
    return [num for num in range(start, end + 1) if is_prime(num)]

# Usage
primes = generate_primes(10, 50)
print(f"Primes in range: {primes}")Code language: Python (python)

Explanation of Solution:

  • int(n**0.5) + 1: This is the mathematical shortcut. If a factor exists, at least one must be less than or equal to the square root of n.
  • range(start, end + 1): We add 1 to the end because Python’s range is exclusive of the final value.
  • List Comprehension: We use a clean one-liner to filter the range based on our is_prime helper function.

Exercise 58: Email Validation Program

Practice Problem: Create a script that asks for an email address and validates it against a comprehensive Regular Expression. It should check for a username, the @ symbol, a domain name, and a valid top-level domain (like .com or .org).

Exercise Purpose: While basic string methods can find an “@” sign, only Regular Expressions (Regex) can ensure the structure of the email is truly valid according to web standards.

Given Input:

email1 = "python_pro@gmail.com"
email2 = "bad-email@com"Code language: Python (python)

Expected Output:

'python_pro@gmail.com' is a Valid Email
'bad-email@com' is an Invalid Email
+ Hint
  • Use the re module and the match() function.
  • A common pattern is ^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$.
+ Show Solution
import re

def validate_email(email):
    # Pattern: [name] @ [domain] . [extension]
    regex = r'^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$'
    
    if re.search(regex, email):
        print(f"'{email}' is a Valid Email")
    else:
        print(f"'{email}' is an Invalid Email")

# Usage
validate_email("python_pro@gmail.com")
validate_email("bad-email@com")Code language: Python (python)

Explanation of Solution:

  • ^ and $: These anchor the search to the start and end of the string, ensuring no extra “garbage” characters are hidden at the ends.
  • \w+: This matches one or more alphanumeric characters.
  • \w{2,3}: This ensures the extension (like .com or .in) is at least 2 or 3 characters long.

Exercise 59: File-Based To-Do List App

Practice Problem: Build a simple CLI app that allows users to add tasks to a text file, view the current list, and “clear” the list. The tasks should persist even after the program is closed.

Exercise Purpose: This introduces Persistent Storage. Instead of data disappearing when the script ends (RAM), we save it to the hard drive (Disk).

Given Input:

Add Task: "Buy Milk"
Add Task: "Finish Python Project"

Expected Output:

--- My To-Do List --- 
1. Buy Milk
2. Finish Python Project
+ Hint
  • Use a (append) mode to add tasks without deleting old ones.
  • Use r (read) mode to display them.
+ Show Solution
FILE_NAME = "todo.txt"

def add_task(task):
    with open(FILE_NAME, "a") as f:
        f.write(task + "\n")

def view_tasks():
    try:
        with open(FILE_NAME, "r") as f:
            print("\n--- Current Tasks ---")
            for i, line in enumerate(f, 1):
                print(f"{i}. {line.strip()}")
    except FileNotFoundError:
        print("No tasks found.")

def clear_tasks():
    open(FILE_NAME, "w").close() # Opening in 'w' mode clears the file
    print("List cleared.")

# Usage
add_task("Code in Python")
add_task("Walk the dog")
view_tasks()Code language: Python (python)

Explanation of Solution:

  • "a" Mode: This “appends” the new text to the end of the existing file.
  • strip(): When reading from a file, each line ends with a newline character (\n). strip() removes that extra whitespace so our display looks clean.
  • open(FILE_NAME, "w").close(): Opening a file in “write” mode immediately wipes the contents. Closing it immediately leaves us with a clean, empty file.

Exercise 60: Random Password Generator

Practice Problem: Write a script that generates a random, secure password. The user should be able to specify the desired length, and the password must include a mix of uppercase letters, lowercase letters, numbers, and symbols.

Exercise Purpose: This project teaches you to use the secrets module (which is more secure than random for passwords) and the string module, which contains pre-defined constants like all letters and punctuation.

Given Input: Length: 12

Expected Output: Generated Password: 4k#79!zP[2mQ

+ Hint

Combine string.ascii_letters, string.digits, and string.punctuation into one pool of characters.

+ Show Solution
import secrets
import string

def generate_password(length):
    if length < 4:
        return "Length too short!"
    
    # Define the character pool
    pool = string.ascii_letters + string.digits + string.punctuation
    
    # Generate password using secrets for cryptographic security
    password = "".join(secrets.choice(pool) for _ in range(length))
    return password

# Usage
print(f"Secure Password: {generate_password(16)}")Code language: Python (python)

Explanation of Solution:

  • secrets.choice(): Unlike random.choice(), the secrets module generates numbers that are cryptographically secure, making them much harder to predict for hackers.
  • "".join(...): This takes the individual random characters generated by our loop and “glues” them together into a single string.
  • string.punctuation: This is a built-in Python string that contains all special characters like !"#$%&'()*+,-./:;<=>?@[\]^_{|}~.

Exercise 61: Pascal’s Triangle

Practice Problem: Write a function to generate the first N rows of Pascal’s Triangle. Each number is the sum of the two numbers directly above it. See print patterns in Python.

Exercise Purpose: This is a classic algorithmic challenge that tests your ability to manage indices and nested lists. It requires thinking about the relationship between a current row and the row that came before it.

Given Input: Rows: 5

Expected Output:

[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
+ Hint
  • Every row starts and ends with 1.
  • The middle numbers are calculated as row[j] = prev_row[j-1] + prev_row[j].
+ Show Solution
def generate_pascal(n):
    triangle = [[1]]
    
    for i in range(1, n):
        prev_row = triangle[-1]
        # Start the row with 1
        new_row = [1]
        
        # Calculate the middle elements
        for j in range(1, i):
            new_row.append(prev_row[j-1] + prev_row[j])
        
        # End the row with 1
        new_row.append(1)
        triangle.append(new_row)
        
    return triangle

# Usage
for row in generate_pascal(5):
    print(row)Code language: Python (python)

Explanation of Solution:

  • triangle[-1]: This is a Pythonic way to grab the very last row we generated.
  • The j Loop: This inner loop only runs for rows larger than 2, calculating the “inner” numbers by looking at the two numbers above them.
  • List of Lists: The final structure is a nested list where each sub-list represents a row, which is perfect for representing mathematical matrices or triangles.

Exercise 62: Smart Property Decorators

Practice Problem: Create a Circle class with a radius attribute. Implement area as a property. If the user changes the radius, the area should reflect the change automatically without needing a manual function call.

Exercise Purpose: This demonstrates Reactive Programming within OOP. Properties allow you to expose calculated data as if it were a simple variable, ensuring that your object’s internal state is always consistent.

Given Input: c = Circle(5), then c.radius = 10

Expected Output: Initial Area: 78.54 Updated Area: 314.16

+ Hint

Use the @property decorator on the area method. This makes it “read-only” and auto-calculating.

+ Show Solution
import math

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        # This re-calculates every time the property is accessed
        return math.pi * (self.radius ** 2)

# Usage
c = Circle(5)
print(f"Area with radius 5: {c.area:.2f}")

c.radius = 10
print(f"Area with radius 10: {c.area:.2f}")Code language: Python (python)

Explanation of Solution:

  • @property: This turns the method into a “getter.” Note that in the print statement, we use c.area instead of c.area().
  • State Sync: Because area is a function under the hood, it doesn’t “store” the old area; it always calculates it based on the current self.radius.
  • Encapsulation: This approach protects the data; the user cannot manually set c.area = 500 because no setter was defined, preventing logical errors.

Exercise 63: Caesar Cipher Implementation

Practice Problem: Implement a function that encrypts a message by shifting each letter by a fixed number of positions in the alphabet. It should handle uppercase, lowercase, and ignore non-alphabetic characters.

Exercise Purpose: This is an introduction to Algorithms and Character Encoding. It teaches you how to use ord() (char to integer) and chr() (integer to char) while using the modulo operator to “wrap around” the alphabet from Z back to A.

Given Input: Message: "Hello!", Shift: 3

Expected Output: "Khoor!"

+ Hint

For any character, the formula is (original_position + shift) % 26.

+ Show Solution
def caesar_cipher(text, shift):
    result = ""
    for char in text:
        if char.isalpha():
            # Determine if we start at 'A' or 'a'
            start = ord('A') if char.isupper() else ord('a')
            # Calculate new position within 0-25 range
            new_pos = (ord(char) - start + shift) % 26
            result += chr(start + new_pos)
        else:
            result += char # Keep punctuation/spaces as is
    return result

# Usage
encrypted = caesar_cipher("Hello World!", 3)
print(f"Encrypted: {encrypted}")
print(f"Decrypted: {caesar_cipher(encrypted, -3)}")Code language: Python (python)

Explanation of Solution:

  • ord(char) - start: This converts the character’s ASCII value to a 0–25 index (e.g., ‘A’ becomes 0).
  • % 26: This is the “circular” logic. If the shift moves ‘Z’ (index 25) by 1, the modulo brings it back to 0 (‘A’).
  • Symmetry: To decrypt, you simply pass the negative of the shift value.

Exercise 64: Hollow Triangle Pattern

Practice Problem: Write a script that prints a hollow triangle of height N. The edges should consist of stars (*), but the interior should be empty space.

Exercise Purpose: This challenges your Coordinate Logic. You must determine the exact conditions under which a star should be printed (the first column, the last column, or the last row) versus a space.

Given Input: Height: 5

Expected Output:

    *    
* *
* *
* *
*********
+ Hint

Use two nested loops. Print a star if it’s the first or last character of a row, or if it’s the bottom row. Otherwise, print a space.

+ Show Solution
def print_hollow_triangle(n):
    for i in range(1, n + 1):
        for j in range(1, 2 * n):
            # Condition for the two sides of the triangle and the base
            if j == n - i + 1 or j == n + i - 1 or i == n:
                print("*", end="")
            else:
                print(" ", end="")
        print() # New line

print_hollow_triangle(5)Code language: Python (python)

Explanation of Solution:

  • j == n - i + 1: This calculates the left-sloping edge.
  • j == n + i - 1: This calculates the right-sloping edge.
  • i == n: This ensures the bottom row is filled completely.
  • end="": This prevents Python from adding a newline after every single character, allowing us to build the row horizontally.

Exercise 65: Solid Diamond Pattern

Practice Problem: Create a script that prints a solid diamond. The user provides the height of the top half, and the program must mirror it to create the bottom half.

Exercise Purpose: This exercise focuses on Symmetry and Loop Ranges. You must learn how to reverse a range (growth vs. shrink) to ensure the diamond aligns perfectly at its widest point.

Given Input:

Size: 4

Expected Output:

   *
***
*****
*******
*****
***
*
+ Hint
  • Split the problem into two loops.
  • The first loop goes from 1 to n, and the second loop goes from n-1 down to 1.
+ Show Solution
def print_diamond(n):
    # Top Half (Growth)
    for i in range(1, n + 1):
        print(" " * (n - i) + "*" * (2 * i - 1))
    
    # Bottom Half (Shrink)
    for i in range(n - 1, 0, -1):
        print(" " * (n - i) + "*" * (2 * i - 1))

print_diamond(4)Code language: Python (python)

Explanation of Solution:

  • tring Multiplication: Python allows you to do " " * 5, which makes centering patterns much easier than using nested loops for spaces.
  • 2 * i - 1: This mathematical formula ensures that every row has an odd number of stars (1, 3, 5, 7), which is required for a centered diamond.
  • range(n-1, 0, -1): The third parameter -1 tells the loop to count backward, creating the inverted triangle for the bottom half.

Filed Under: Python, Python Exercises

Did you find this page helpful? Let others know about it. Sharing helps me continue to create free Python resources.

TweetF  sharein  shareP  Pin

About Vishal

Image

I’m Vishal Hule, the Founder of PYnative.com. As a Python developer, I enjoy assisting students, developers, and learners. Follow me on Twitter.

Related Tutorial Topics:

Python Python Exercises

All Coding Exercises:

C Exercises
C++ Exercises
Python Exercises

Python Exercises and Quizzes

Free coding exercises and quizzes cover Python basics, data structure, data analytics, and more.

  • 15+ Topic-specific Exercises and Quizzes
  • Each Exercise contains 25+ questions
  • Each Quiz contains 25 MCQ
Exercises
Quizzes

Loading comments... Please wait.

In: Python Python Exercises
TweetF  sharein  shareP  Pin

  Python Exercises

  • All Python Exercises
  • Basic Exercise for Beginners
  • Intermediate Python Exercises
  • Input and Output Exercise
  • Loop Exercise
  • Functions Exercise
  • String Exercise
  • Data Structure Exercise
  • List Exercise
  • Dictionary Exercise
  • Set Exercise
  • Tuple Exercise
  • Date and Time Exercise
  • OOP Exercise
  • File Handling Exercise
  • Python JSON Exercise
  • Random Data Generation Exercise
  • NumPy Exercise
  • Pandas Exercise
  • Matplotlib Exercise
  • Python Database Exercise

 Explore Python

  • Python Tutorials
  • Python Exercises
  • Python Quizzes
  • Python Interview Q&A
  • Python Programs

All Python Topics

Python Basics Python Exercises Python Quizzes Python Interview Python File Handling Python OOP Python Date and Time Python Random Python Regex Python Pandas Python Databases Python MySQL Python PostgreSQL Python SQLite Python JSON

About PYnative

PYnative.com is for Python lovers. Here, You can get Tutorials, Exercises, and Quizzes to practice and improve your Python skills.

Follow Us

To get New Python Tutorials, Exercises, and Quizzes

  • Twitter
  • Facebook
  • Sitemap

Explore Python

  • Learn Python
  • Python Basics
  • Python Databases
  • Python Exercises
  • Python Quizzes
  • Online Python Code Editor
  • Python Tricks

Coding Exercises

  • C Exercises
  • C++ Exercises
  • Python Exercises

Legal Stuff

  • About Us
  • Contact Us

We use cookies to improve your experience. While using PYnative, you agree to have read and accepted our:

  • Terms Of Use
  • Privacy Policy
  • Cookie Policy

Copyright © 2018–2026 pynative.com

Advertisement