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 » Python Lambda & Functional Programming Exercises with Solutions

Python Lambda & Functional Programming Exercises with Solutions

Updated on: June 4, 2026 | Leave a Comment

This article contains 20 Python Lambda & Functional Programming coding practice questions including map(), filter(), reduce(), functools from beginner to advance level.

Each coding challenge includes a Practice Problem, Hint, Solution code, and detailed Explanation, ensuring you don’t just copy code, but genuinely practice and understand how and why it works.

  • All solutions have been fully tested on Python 3.
  • Use our Online Code Editor to solve these exercises in real time.
+ Table of Contents (20 Exercises)

Table of contents

  • Exercise 1: Basic Lambda
  • Exercise 2: Lambda with Sorting
  • Exercise 3: Map Basics
  • Exercise 4: Filter Basics
  • Exercise 5: Reduce Basics
  • Exercise 6: Conditional Lambda
  • Exercise 7: Lambda in a Dictionary
  • Exercise 8: Chaining Map & Filter
  • Exercise 9: Sorted with Multiple Keys
  • Exercise 10: Partial Functions
  • Exercise 11: Function Composition
  • Exercise 12: Flatten with Map & Reduce
  • Exercise 13: Custom Key with sorted()
  • Exercise 14: Memoization with lru_cache
  • Exercise 15: Currying
  • Exercise 16: Pipeline Function
  • Exercise 17: Transducer Pattern
  • Exercise 18: Lazy Evaluation with Generators
  • Exercise 19: Higher-Order Function Library
  • Exercise 20: Monadic Chaining

Exercise 1: Basic Lambda

Problem Statement: Write a lambda function that takes a single number and returns its square. Assign the lambda to a variable named square and call it with a few different values to verify it works.

Purpose: This exercise introduces the lambda keyword, Python’s syntax for creating small anonymous functions inline. Understanding lambdas is the foundation for using map(), filter(), sorted(), and other higher-order functions effectively, since all of them accept a callable as an argument.

Given Input: Call square(4) and square(9).

Expected Output: 16 and 81

▼ Hint

The syntax is lambda parameter: expression. The expression is evaluated and returned automatically – no return keyword is needed or allowed.

▼ Solution & Explanation
square = lambda x: x ** 2

print(square(4))   # 16
print(square(9))   # 81Code language: Python (python)

Explanation:

  • lambda x: x ** 2: Creates an anonymous function that takes one parameter x and returns x ** 2. The entire expression to the right of the colon is the implicit return value.
  • Assigning to a variable: Storing a lambda in a named variable like square makes it callable exactly like a regular def function. The result is functionally identical to def square(x): return x ** 2.
  • When to use lambda vs. def: The Python style guide (PEP 8) recommends against assigning lambdas to variables at module level – use def for named functions. Lambdas shine as inline, one-off callables passed directly to another function, as the following exercises demonstrate.
  • Limitations: A lambda body must be a single expression. It cannot contain statements, assignments, loops, or multiple lines. For anything more complex, a regular def function is the right tool.

Exercise 2: Lambda with Sorting

Problem Statement: Given a list of (name, age) tuples, sort them in ascending order by age using sorted() with a lambda as the key argument. Then sort the same list in descending order by age.

Purpose: Sorting by a custom criterion is one of the most common everyday tasks in Python. This exercise shows how a lambda eliminates the need to define a separate helper function just to extract a sort key, making the intent clear and the code concise.

Given Input: people = [("Alice", 30), ("Bob", 25), ("Charlie", 35), ("Diana", 28)]

Expected Output:

Ascending:  [('Bob', 25), ('Diana', 28), ('Alice', 30), ('Charlie', 35)]
Descending: [('Charlie', 35), ('Alice', 30), ('Diana', 28), ('Bob', 25)]
▼ Hint
  • Pass key=lambda person: person[1] to sorted(). The key function receives each element and returns the value Python should compare for ordering.
  • Add reverse=True to the same sorted() call for descending order.
▼ Solution & Explanation
people = [("Alice", 30), ("Bob", 25), ("Charlie", 35), ("Diana", 28)]

ascending  = sorted(people, key=lambda person: person[1])
descending = sorted(people, key=lambda person: person[1], reverse=True)

print("Ascending: ", ascending)
print("Descending:", descending)Code language: Python (python)

Explanation:

  • key=lambda person: person[1]: The key parameter accepts any callable. Python calls it once per element and uses the returned value for comparison. Here, person[1] extracts the age from each tuple, so the list is sorted by age rather than by the default tuple comparison (which would sort by name first).
  • sorted() vs. .sort(): sorted() returns a new list and leaves the original unchanged. list.sort() sorts in place and returns None. Both accept the same key and reverse parameters.
  • reverse=True: Reverses the sort direction without requiring a separate lambda. It is more efficient than sorting ascending and then reversing the list afterwards.
  • Alternative – operator.itemgetter: from operator import itemgetter followed by key=itemgetter(1) is slightly faster than a lambda for simple index extraction because it avoids Python function call overhead. For most use cases the difference is negligible, and the lambda is more readable.

Exercise 3: Map Basics

Problem Statement: Use map() with a lambda to convert a list of temperatures from Celsius to Fahrenheit. The conversion formula is F = (C × 9/5) + 32. Collect the results into a new list and print it.

Purpose: map() applies a transformation to every element of an iterable without writing an explicit loop. This is the core idea behind functional programming: describing what to do to data rather than how to iterate over it. The pattern scales cleanly from lists to generators, files, and database result sets.

Given Input: celsius = [0, 20, 37, 100]

Expected Output: [32.0, 68.0, 98.6, 212.0]

▼ Hint
  • Call map(lambda c: (c * 9/5) + 32, celsius). map() returns a lazy iterator, so wrap it in list() to get a concrete list you can print.
  • The lambda takes a single Celsius value c and applies the formula directly in the expression.
▼ Solution & Explanation
celsius = [0, 20, 37, 100]

fahrenheit = list(map(lambda c: (c * 9 / 5) + 32, celsius))

print(fahrenheit)   # [32.0, 68.0, 98.6, 212.0]Code language: Python (python)

Explanation:

  • map(function, iterable): Applies function to each element of iterable lazily. The function is not called until the iterator is consumed – either by list(), a for loop, or another consuming operation.
  • list(map(...)): Forces evaluation of the lazy iterator and collects all results into a list. Without wrapping in list(), printing the result would show something like <map object at 0x...> rather than the values.
  • Lambda as the transform: The lambda lambda c: (c * 9 / 5) + 32 encodes the conversion formula in a single expression. Because it has no name and is used in exactly one place, a lambda is more appropriate here than a separate def.
  • List comprehension equivalent: [(c * 9 / 5) + 32 for c in celsius] produces exactly the same result. Many Python developers prefer list comprehensions for their readability, but map() is more composable when chaining multiple transformations or working with very large datasets where lazy evaluation matters.

Exercise 4: Filter Basics

Problem Statement: Use filter() with a lambda to extract only the even numbers from a list of integers. Collect the results into a new list and print it.

Purpose: filter() is the functional equivalent of a loop with an if condition inside. It keeps only the elements for which a predicate function returns True. Separating the filtering logic from the iteration makes code easier to test, reuse, and reason about.

Given Input: numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Expected Output: [2, 4, 6, 8, 10]

▼ Hint
  • Call filter(lambda n: n % 2 == 0, numbers). Like map(), filter() returns a lazy iterator, so wrap it in list().
  • The lambda is a predicate: it returns True for even numbers and False for odd ones. Only elements where the lambda returns a truthy value are kept.
▼ Solution & Explanation
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

evens = list(filter(lambda n: n % 2 == 0, numbers))

print(evens)   # [2, 4, 6, 8, 10]Code language: Python (python)

Explanation:

  • filter(function, iterable): Calls function on each element and yields only those for which it returns a truthy value. Passing None as the function is also valid – it then keeps all elements that are truthy themselves.
  • n % 2 == 0: The modulo operator % returns the remainder after division. A remainder of 0 when dividing by 2 means the number is even. The expression evaluates directly to True or False, which is exactly what a predicate needs to return.
  • Lazy evaluation: Like map(), filter() does not process any elements until iterated. This makes it memory-efficient for large datasets – wrapping in list() forces full evaluation only when you actually need all results at once.
  • List comprehension equivalent: [n for n in numbers if n % 2 == 0] is the idiomatic Python alternative. As with map(), prefer filter() when composing multiple functional operations in a pipeline, and list comprehensions for simple one-off filtering where readability is the priority.

Exercise 5: Reduce Basics

Problem Statement: Use functools.reduce() with a lambda to find the product of all numbers in a list. The function should multiply each element cumulatively from left to right until a single value remains.

Purpose: reduce() is the functional tool for collapsing an iterable into a single accumulated value. While Python offers sum() and max() as built-ins for common reductions, reduce() handles any binary operation including product, string concatenation, and custom aggregation logic.

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

Expected Output: 120

▼ Hint
  • Import reduce from functools first: from functools import reduce.
  • Call reduce(lambda acc, n: acc * n, numbers). The lambda takes two arguments: the accumulated value so far (acc) and the current element (n). It returns the new accumulated value, which is fed back in as acc on the next call.
▼ Solution & Explanation
from functools import reduce

numbers = [1, 2, 3, 4, 5]

product = reduce(lambda acc, n: acc * n, numbers)

print(product)   # 120Code language: Python (python)

Explanation:

  • reduce(function, iterable): Applies function cumulatively. It starts by calling function(iterable[0], iterable[1]), then calls function(result, iterable[2]), and so on until the iterable is exhausted. The final return value is a single scalar.
  • lambda acc, n: acc * n: The two-argument lambda represents the binary operation being folded across the list. On the first call acc = 1 and n = 2, returning 2. On the second, acc = 2 and n = 3, returning 6. This continues until the product of all five numbers – 120 – is produced.
  • Optional initial value: reduce() accepts an optional third argument as the starting accumulator: reduce(lambda acc, n: acc * n, numbers, 1). This is useful when the list might be empty – without an initial value, reduce() raises TypeError on an empty iterable.
  • Why not math.prod()?: Python 3.8 added math.prod(numbers) as a built-in for exactly this case. Use it in production. The reduce() approach is taught here because it generalises to any binary operation and demonstrates the fold pattern that underpins many functional programming concepts.

Exercise 6: Conditional Lambda

Problem Statement: Write a lambda that takes a single integer and returns the string "even" if the number is divisible by 2, or "odd" otherwise. Assign it to a variable named parity and test it on several values.

Purpose: Lambda expressions support Python’s inline conditional (ternary) syntax, making it possible to express simple branching logic without a full def block. This pattern appears frequently as the key or transform argument in map(), sorted(), and similar functions where a one-line decision is needed.

Given Input: Call parity(4), parity(7), and parity(0).

Expected Output: even, odd, even

▼ Hint

Use Python’s inline conditional inside the lambda: lambda n: "even" if n % 2 == 0 else "odd". The expression evaluates to one of the two string values depending on the condition.

▼ Solution & Explanation
parity = lambda n: "even" if n % 2 == 0 else "odd"

print(parity(4))   # even
print(parity(7))   # odd
print(parity(0))   # evenCode language: Python (python)

Explanation:

  • "even" if n % 2 == 0 else "odd": This is Python’s ternary (conditional) expression. It evaluates to the value before if when the condition is true, and the value after else when it is false. Because it is a single expression, it fits naturally inside a lambda body.
  • parity(0) returns "even": Zero is divisible by 2 with no remainder (0 % 2 == 0), so it correctly classifies as even. This is consistent with the mathematical definition of even numbers.
  • Composing with map(): Because parity is a callable, it can be passed directly to map(): list(map(parity, [1, 2, 3, 4])) produces ['odd', 'even', 'odd', 'even']. This illustrates how named lambdas become reusable building blocks for functional pipelines.
  • Readability note: Nested ternary expressions inside a lambda (e.g., deciding between three outcomes) quickly become hard to read. If the logic requires more than one condition, use a def function with explicit if/elif/else branches instead.

Exercise 7: Lambda in a Dictionary

Problem Statement: Store four lambda functions in a dictionary under the keys "add", "sub", "mul", and "div". Each lambda should take two numbers and perform the corresponding arithmetic operation. Use the dictionary to build a simple calculator that looks up the operation by key and applies it to two operands.

Purpose: Storing callables in a dictionary is a clean alternative to long if/elif chains for dispatching operations. This pattern – sometimes called a dispatch table – is used in command parsers, plugin systems, and menu-driven programs. Combining it with lambdas keeps the entire definition concise.

Given Input: Apply each of the four operations to 10 and 3.

Expected Output:

add: 13
sub: 7
mul: 30
div: 3.3333333333333335
▼ Hint
  • Define the dictionary with string keys and lambda values: {"add": lambda a, b: a + b, ...}. Each lambda takes two parameters.
  • To call an operation, look it up and invoke it: ops["add"](10, 3). The square-bracket lookup returns the lambda, and the parentheses immediately call it with the arguments.
▼ Solution & Explanation
ops = {
    "add": lambda a, b: a + b,
    "sub": lambda a, b: a - b,
    "mul": lambda a, b: a * b,
    "div": lambda a, b: a / b,
}

a, b = 10, 3

for name, func in ops.items():
    print(f"{name}: {func(a, b)}")Code language: Python (python)

Explanation:

  • Lambdas as dictionary values: In Python, functions are first-class objects – they can be stored in variables, lists, and dictionaries just like integers or strings. Each lambda here is a value associated with a string key, retrievable and callable at any time.
  • ops["add"](10, 3): The lookup ops["add"] returns the lambda object itself. Appending (10, 3) immediately calls it with those arguments. This is identical to calling a function stored in any other variable.
  • Dispatch table pattern: Iterating over ops.items() applies every operation in one loop without any if/elif branching. Adding a new operation requires only a new dictionary entry – no changes to the loop or calling code. This open-closed design makes dispatch tables easy to extend.
  • Extending the calculator: You can add operations like "mod": lambda a, b: a % b or "pow": lambda a, b: a ** b without modifying any existing code. For a user-facing calculator, you would look up the key from user input: op = input("Operation: "); result = ops[op](a, b) – adding a KeyError check for unknown operations.

Exercise 8: Chaining Map & Filter

Problem Statement: Given a list of integers that includes negative numbers, use filter() to remove all negatives, then use map() to square the remaining values. Chain the two operations so no intermediate named variable is required.

Purpose: Real data pipelines rarely apply just one transformation. This exercise shows how filter() and map() compose naturally by passing the output of one directly as the input of the other — producing a readable, loop-free pipeline that processes data in a single pass.

Given Input: numbers = [-3, -1, 0, 2, 4, -2, 5, 7]

Expected Output: [0, 4, 16, 25, 49]

▼ Hint
  • Apply filter(lambda n: n >= 0, numbers) first to keep only non-negative values. Because filter() returns a lazy iterator, you can pass it directly to map() without converting to a list first.
  • Wrap the filter() call as the second argument to map(lambda n: n ** 2, ...). Finish by wrapping the whole expression in list() to evaluate it.
▼ Solution & Explanation
numbers = [-3, -1, 0, 2, 4, -2, 5, 7]

result = list(
    map(lambda n: n ** 2,
        filter(lambda n: n >= 0, numbers))
)

print(result)   # [0, 4, 16, 25, 49]Code language: Python (python)

Explanation:

  • Evaluation order: Python evaluates arguments from the inside out. filter() runs first and produces a lazy iterator of non-negative values. That iterator is passed directly to map(), which wraps it in another lazy iterator that squares each value. list() at the outermost level triggers full evaluation.
  • Zero is kept: The predicate n >= 0 includes zero, so 0 passes the filter and 0 ** 2 = 0 appears in the output. If you want to exclude zero as well, change the condition to n > 0.
  • Single-pass efficiency: Because both filter() and map() are lazy, each element is processed only once as list() pulls values through the chain. There is no intermediate list allocated between the two steps, which matters for large datasets.
  • List comprehension equivalent: [n ** 2 for n in numbers if n >= 0] is the idiomatic Python alternative and is often preferred for its readability. The map()/filter() chain is worth knowing because it mirrors the functional style used in languages like Haskell and Scala, and it composes cleanly when you need more than two stages.

Exercise 9: Sorted with Multiple Keys

Problem Statement: Given a list of employee dictionaries, each with a "name" and a "salary" key, sort the list by salary in descending order. Where two employees share the same salary, sort those entries by name in ascending alphabetical order. Use a single lambda as the key argument.

Purpose: Sorting on more than one criterion is a common real-world requirement: leaderboards, reports, and data exports all need it. This exercise shows the tuple-key trick that lets a single lambda express a multi-level sort without any additional imports or helper functions.

Given Input: employees = [{"name": "Alice", "salary": 70000}, {"name": "Bob", "salary": 90000}, {"name": "Charlie", "salary": 70000}, {"name": "Diana", "salary": 90000}]

Expected Output:

Bob        $90000
Diana      $90000
Alice      $70000
Charlie    $70000
▼ Hint
  • Return a tuple from the lambda: lambda e: (-e["salary"], e["name"]). Python sorts tuples element by element, so the first element controls the primary sort and the second controls the tiebreaker.
  • Negate the salary (-e["salary"]) to achieve descending order without using reverse=True, which would also reverse the name sort direction and break the tiebreaker.
▼ Solution & Explanation
employees = [
    {"name": "Alice",   "salary": 70000},
    {"name": "Bob",     "salary": 90000},
    {"name": "Charlie", "salary": 70000},
    {"name": "Diana",   "salary": 90000},
]
sorted_employees = sorted(
    employees,
    key=lambda e: (-e["salary"], e["name"])
)
for emp in sorted_employees:
    print(f"{emp['name']:<10} ${emp['salary']}")Code language: Python (python)

Explanation:

  • Tuple key: When the key function returns a tuple, Python compares tuples lexicographically: it compares the first element, and only moves to the second if the first elements are equal. Returning (-salary, name) means salary is the primary sort criterion and name is the tiebreaker.
  • Negating for descending order: Negating a numeric key (-e["salary"]) inverts the sort direction for that field only. A higher salary becomes a more negative number, so it sorts to the front. This technique works for any numeric field and avoids the complication of reverse=True, which would flip all sort levels simultaneously.
  • Stability of Python’s sort: Python’s sorted() is a stable sort (Timsort). Elements that compare equal under the key function retain their original relative order. The tuple key makes equal-salary ties explicit, but stability would preserve insertion order even without the name component.
  • Negation limitation: The negation trick only works for numeric fields. To sort a string field descending, you would need a different approach – such as using functools.cmp_to_key with a custom comparator, or sorting twice (Python’s stable sort guarantees correctness when you do a secondary sort first and a primary sort second).

Exercise 10: Partial Functions

Problem Statement: Write a general multiply(x, n) function that returns x * n. Use functools.partial() to create two specialised functions from it: double(x), which always multiplies by 2, and triple(x), which always multiplies by 3. Call both with several values.

Purpose: partial() lets you lock in one or more arguments of an existing function to produce a simpler, more focused callable. This avoids writing wrapper functions by hand and is particularly useful when an API expects a one-argument callable but your logic needs additional context baked in.

Given Input: Call double(5), double(9), triple(4), and triple(7).

Expected Output:

double(5) = 10
double(9) = 18
triple(4) = 12
triple(7) = 21
▼ Hint
  • Import partial from functools: from functools import partial.
  • Create the specialised functions with partial(multiply, n=2) and partial(multiply, n=3). The second argument to partial() is the value you want pre-filled for the n parameter of multiply.
  • The resulting double and triple objects are fully callable — pass them a single argument x and they behave exactly like a one-parameter function.
▼ Solution & Explanation
from functools import partial

def multiply(x, n):
    return x * n

double = partial(multiply, n=2)
triple = partial(multiply, n=3)

print(f"double(5) = {double(5)}")   # 10
print(f"double(9) = {double(9)}")   # 18
print(f"triple(4) = {triple(4)}")   # 12
print(f"triple(7) = {triple(7)}")   # 21Code language: Python (python)

Explanation:

  • partial(func, *args, **kwargs): Returns a new callable that behaves like func called with the given arguments pre-filled. Any additional arguments passed when calling the partial are appended to (or merged with) the pre-filled ones.
  • Keyword vs. positional pre-filling: Using partial(multiply, n=2) pre-fills n by name, leaving x to be provided at call time. You could also use positional pre-filling: partial(multiply, 5) would lock x=5 and leave n free — the direction depends on argument order and which parameter you want to specialise.
  • Practical use cases: partial() shines when passing callbacks to APIs that expect a fixed signature. For example, map(partial(multiply, n=10), numbers) scales every number in a list by 10 without needing a dedicated lambda or def.
  • Inspecting a partial: The resulting object exposes double.func (the original function), double.args (pre-filled positional arguments), and double.keywords (pre-filled keyword arguments), making it straightforward to introspect or log what a partial was built from.

Exercise 11: Function Composition

Problem Statement: Write a compose(f, g) utility function that returns a new function equivalent to applying g first and then f to the result — that is, compose(f, g)(x) should equal f(g(x)). Test it by composing a lambda that doubles a number with a lambda that adds 3.

Purpose: Function composition is a fundamental concept in functional programming. Rather than calling functions one by one in sequence, composition lets you build a new function by wiring existing ones together. The result is more reusable, more testable, and reads like a description of a pipeline rather than a sequence of instructions.

Given Input: Compose double = lambda x: x * 2 and add_three = lambda x: x + 3. Call the composed function with 5.

Expected Output: double(add_three(5)) = 16

▼ Hint
  • compose(f, g) should return a lambda (or inner function) that takes x and returns f(g(x)). The returned callable closes over f and g.
  • Test the direction carefully: in mathematical notation, (f ∘ g)(x) = f(g(x)) means g is applied first. Verify with a simple example before building on it.
▼ Solution & Explanation
def compose(f, g):
    return lambda x: f(g(x))

double    = lambda x: x * 2
add_three = lambda x: x + 3

double_after_add = compose(double, add_three)

print(f"double(add_three(5)) = {double_after_add(5)}")   # 16

# Reverse order for comparison
add_after_double = compose(add_three, double)
print(f"add_three(double(5)) = {add_after_double(5)}")   # 13Code language: Python (python)

Explanation:

  • return lambda x: f(g(x)): The inner lambda closes over f and g from the enclosing compose call. Each time the returned function is called with a value x, it evaluates g(x) first, then passes that result to f.
  • Order matters: compose(double, add_three)(5) computes double(add_three(5)): add 3 to get 8, then double to get 16. Reversing to compose(add_three, double)(5) gives add_three(double(5)): double to get 10, then add 3 to get 13. Composition is not commutative.
  • Extending to multiple functions: A variadic composer can chain any number of functions using reduce(): from functools import reduce; def compose_many(*fns): return reduce(compose, fns). This applies the rightmost function first and works its way left, matching standard mathematical notation.
  • Real-world relevance: Function composition appears in data transformation pipelines, middleware stacks, and decorator chains. Python’s own decorator syntax (@decorator) is essentially syntactic sugar for a specific kind of function composition where the outer function wraps the inner one.

Exercise 12: Flatten with Map & Reduce

Problem Statement: Given a list of lists, use functools.reduce() with a lambda to flatten it into a single list. Do not use any for loops, list comprehensions, or itertools.

Purpose: Flattening a nested structure is a classic exercise in functional reduction. Solving it with reduce() reinforces how any operation that accumulates a result across a sequence — not just arithmetic — can be expressed as a fold. It also demonstrates that the accumulator in reduce() can be any type, not just a number.

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

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

▼ Hint
  • Use reduce(lambda acc, sublist: acc + sublist, nested). The + operator on two lists concatenates them, so each iteration appends the next sublist to the growing accumulator.
  • Pass [] as the optional third argument to reduce() to set the initial accumulator to an empty list. Without it, the first sublist becomes the starting value, which works here but is safer to make explicit.
▼ Solution & Explanation
from functools import reduce

nested = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]

flat = reduce(lambda acc, sublist: acc + sublist, nested, [])

print(flat)   # [1, 2, 3, 4, 5, 6, 7, 8, 9]Code language: Python (python)

Explanation:

  • lambda acc, sublist: acc + sublist: On each step, acc holds the flattened list built so far and sublist is the next inner list. The + operator creates a new concatenated list, which becomes the next acc. After all three sublists are processed, acc holds the fully flattened result.
  • Initial value []: Providing an empty list as the third argument guarantees the accumulator starts as a list even if nested is empty. Without it, reduce() would raise TypeError on an empty input because there is no first element to use as the starting value.
  • Performance note: Each acc + sublist creates a brand-new list, copying all elements accumulated so far. For a large number of sublists this results in O(n²) copies. A more efficient approach is reduce(lambda acc, s: acc.__iadd__(s) or acc, nested, []) using in-place extension, or simply list(itertools.chain.from_iterable(nested)) in production code.
  • Accumulator type flexibility: This exercise illustrates that reduce() is not limited to numbers. The accumulator can be a list, dictionary, string, or any other type — whatever your binary function returns. This makes reduce() a general-purpose fold over any data structure.

Exercise 13: Custom Key with sorted()

Problem Statement: Given a list of strings, sort them alphabetically by their last character using a lambda as the key argument to sorted(). Where two strings end with the same character, preserve their original relative order.

Purpose: The key parameter of sorted() accepts any callable that extracts a sort value from each element. This exercise reinforces that the key does not have to be the element itself — it can be any derived value, including a substring, a computed property, or the result of a function call.

Given Input: words = ["banana", "apple", "cherry", "date", "fig", "kiwi"]

Expected Output: ['banana', 'apple', 'date', 'fig', 'cherry', 'kiwi']

▼ Hint
  • Pass key=lambda s: s[-1] to sorted(). In Python, negative indices count from the end of a sequence, so s[-1] is always the last character of the string.
  • Python’s sort is stable, so words that share the same last character will appear in their original list order relative to each other — no extra work needed for the tiebreaker.
▼ Solution & Explanation
words = ["banana", "apple", "cherry", "date", "fig", "kiwi"]

sorted_words = sorted(words, key=lambda s: s[-1])

print(sorted_words)
# ['banana', 'apple', 'date', 'fig', 'cherry', 'kiwi']Code language: Python (python)

Explanation:

  • lambda s: s[-1]: Extracts the last character of each string. The last characters of the input words are: a (banana), e (apple), y (cherry), e (date), g (fig), i (kiwi). Sorting these alphabetically gives a, e, e, g, i, y, which maps back to the output order.
  • Stable sort handles ties: Both "apple" and "date" end with "e". Because Python’s Timsort is stable, their relative order from the original list is preserved: "apple" came before "date", so it stays before it in the output.
  • Key function called once per element: Python computes the key for each element exactly once before comparing. This means even an expensive key function is not repeatedly called during comparisons — making it safe to use key with slow operations like database lookups or regex matches.
  • Beyond last character: The same pattern generalises to any positional extraction: sort by second character with s[1], by last two characters with s[-2:], or by file extension with s.rsplit(".", 1)[-1]. The lambda is just a projection function — it can express any transformation that produces a comparable value.

Exercise 14: Memoization with lru_cache

Problem Statement: Write a recursive Fibonacci function decorated with @functools.lru_cache. Then write the same function without caching. Use Python’s time module to measure and compare how long each version takes to compute fib(35).

Purpose: Naive recursive Fibonacci has exponential time complexity because it recomputes the same subproblems repeatedly. lru_cache applies memoization automatically: each unique argument is computed once and the result is stored for reuse. This exercise makes the performance difference concrete and introduces one of Python’s most useful built-in optimisation tools.

Given Input: Compute fib(35) with both cached and uncached versions and print the elapsed time for each.

Expected Output:

Cached   fib(35) = 9227465  in 0.000012s
Uncached fib(35) = 9227465  in 2.817043s
▼ Hint
  • Import lru_cache from functools and apply it as a decorator: @lru_cache(maxsize=None) above the function definition. maxsize=None means the cache is unlimited in size.
  • Write the uncached version as a plain recursive function with the same body but without the decorator.
  • Use time.perf_counter() before and after each call to measure elapsed time in seconds with high precision.
▼ Solution & Explanation
import time
from functools import lru_cache
@lru_cache(maxsize=None)
def fib_cached(n):
    if n < 2:
        return n
    return fib_cached(n - 1) + fib_cached(n - 2)
def fib_uncached(n):
    if n < 2:
        return n
    return fib_uncached(n - 1) + fib_uncached(n - 2)
n = 35
start = time.perf_counter()
result = fib_cached(n)
elapsed = time.perf_counter() - start
print(f"Cached   fib({n}) = {result}  in {elapsed:.6f}s")
start = time.perf_counter()
result = fib_uncached(n)
elapsed = time.perf_counter() - start
print(f"Uncached fib({n}) = {result}  in {elapsed:.6f}s")Code language: Python (python)

Explanation:

  • @lru_cache(maxsize=None): Wraps the function with a Least Recently Used cache. On each call, Python checks whether the arguments are already in the cache. If they are, the stored result is returned immediately without executing the function body. maxsize=None disables the eviction policy so every computed value is kept indefinitely — appropriate for a pure function like Fibonacci where results never change.
  • Why the uncached version is slow: Computing fib_uncached(35) calls fib_uncached(34) and fib_uncached(33). Each of those branches calls two more, and so on, producing a call tree with roughly 2³⁵ (about 34 billion) nodes — though many are duplicates. The cached version computes each of the 36 unique inputs (fib(0) through fib(35)) exactly once.
  • time.perf_counter(): Returns a high-resolution timer value in seconds. Taking the difference before and after a function call gives wall-clock elapsed time. It is more precise than time.time() for short durations and is the recommended choice for benchmarking.
  • Python 3.9 shorthand: From Python 3.9 onwards you can use @lru_cache without parentheses (equivalent to maxsize=128) or @functools.cache (equivalent to maxsize=None). The explicit lru_cache(maxsize=None) form used here works on all Python 3 versions and makes the unlimited-cache intent clear.

Exercise 15: Currying

Problem Statement: Implement a curry(func) utility that transforms a two-argument function into a chain of single-argument functions, so that curry(add)(2)(3) returns the same result as add(2, 3). Then extend the idea to a manually curried three-argument function.

Purpose: Currying converts a multi-argument function into a sequence of one-argument functions, each returning the next function in the chain until all arguments have been supplied. This technique enables partial application without functools.partial, makes functions trivially composable, and is a cornerstone concept in functional languages such as Haskell and ML.

Given Input: A two-argument add(a, b) function and a three-argument volume(l, w, h) function.

Expected Output:

curry(add)(2)(3)        = 5
curry(add)(10)(7)       = 17
volume(2)(3)(4)         = 24
box_with_height_4(2)(3) = 24
▼ Hint
  • For a two-argument curry, curry(func) should return lambda a: lambda b: func(a, b). The outer lambda captures a and returns a new lambda waiting for b.
  • For a three-argument version, extend the chain one level deeper: lambda a: lambda b: lambda c: func(a, b, c).
  • Each call in the chain f(2)(3)(4) invokes the outermost lambda with 2, which returns a new lambda; that is called with 3, returning another lambda; finally called with 4 to produce the result.
▼ Solution & Explanation
def curry(func):
    """Curry a two-argument function into f(a)(b)."""
    return lambda a: lambda b: func(a, b)

def add(a, b):
    return a + b

curried_add = curry(add)
print(f"curry(add)(2)(3)  = {curried_add(2)(3)}")    # 5
print(f"curry(add)(10)(7) = {curried_add(10)(7)}")   # 17

# Manually curried three-argument function
def volume(l, w, h):
    return l * w * h

curried_volume = lambda l: lambda w: lambda h: volume(l, w, h)

print(f"volume(2)(3)(4)         = {curried_volume(2)(3)(4)}")    # 24

box_with_height_4 = curried_volume(2)   # l is fixed at 2... wait, fix height
make_box = lambda h: lambda l: lambda w: volume(l, w, h)
box_with_height_4 = make_box(4)
print(f"box_with_height_4(2)(3) = {box_with_height_4(2)(3)}")   # 24Code language: Python (python)

Explanation:

  • lambda a: lambda b: func(a, b): The outer lambda captures func via closure and returns a new function waiting for a. When called with a, it returns another lambda waiting for b. Only when b is supplied does the original function execute. Each step in the chain is a closure that carries the arguments collected so far.
  • Partial application via currying: add5 = curried_add(5) produces a reusable function that adds 5 to any number. This is the same result as functools.partial(add, 5) but achieved through the currying structure itself, without any library support.
  • Argument order matters: In the make_box(4) example, the height is fixed first so the returned function accepts length and width in sequence. Designing curried functions with the most-stable argument first and the most-varying argument last is a functional programming convention that maximises reuse.
  • General-arity currying: The curry() here handles exactly two arguments. A fully general solution inspects func.__code__.co_argcount or uses inspect.signature() to determine how many arguments to collect before calling the original function. Libraries such as toolz provide this as toolz.curry.

Exercise 16: Pipeline Function

Problem Statement: Build a pipeline(*funcs) utility that takes any number of single-argument functions and returns a new function. When called with a value, the returned function passes it through each function in left-to-right order, feeding each result into the next — like a Unix pipe. Demonstrate it by building a text-processing pipeline.

Purpose: A pipeline makes data transformation code read in the same direction as the transformations happen. Instead of deeply nested calls like f(g(h(x))), you write pipeline(h, g, f)(x) where the order of functions matches the order of operations. This pattern is central to data engineering, ETL scripts, and functional-style Python.

Given Input: The string " hello, world! " passed through a pipeline that strips whitespace, converts to title case, replaces the comma, and appends an exclamation mark.

Expected Output: Hello World!

▼ Hint
  • Use functools.reduce() inside pipeline: reduce(lambda value, func: func(value), funcs, initial_value). The accumulator is the running value and each function in funcs is applied to it in turn.
  • Return a lambda from pipeline() that captures funcs and accepts the initial value: return lambda x: reduce(lambda v, f: f(v), funcs, x).
▼ Solution & Explanation
from functools import reduce

def pipeline(*funcs):
    return lambda x: reduce(lambda value, f: f(value), funcs, x)

# Build a text-processing pipeline
process = pipeline(
    str.strip,
    str.title,
    lambda s: s.replace(",", ""),
    lambda s: s.rstrip("!") + "!",
)

result = process("  hello, world!  ")
print(result)   # Hello World!Code language: Python (python)

Explanation:

  • reduce(lambda value, f: f(value), funcs, x): The accumulator starts as x. On each step, the current function f is called with the running value and the result becomes the new value. After all functions have been applied, the final value is returned. This is a left fold where the data flows left to right through the function list.
  • Functions as first-class values: str.strip and str.title are passed as unbound method references without calling them. Each one is a callable that takes a string and returns a string, satisfying the single-argument contract the pipeline requires.
  • Pipeline vs. compose: pipeline(f, g, h) applies f first, then g, then h — left to right. The compose(f, g) from Exercise 11 applies right to left (g first, then f), matching mathematical function composition notation. Both are useful; the choice depends on which reading direction feels more natural for the use case.
  • Reusability: The pipeline object process can be called as many times as needed on different input strings. Steps can be added, removed, or reordered simply by editing the argument list to pipeline(), making the transformation logic easy to adjust without touching calling code.

Exercise 17: Transducer Pattern

Problem Statement: Compose a filter (keep only even numbers) and a map (square each value) into a single reducing function — a transducer — that processes a list in one pass without creating any intermediate list. Apply it manually using functools.reduce().

Purpose: Chaining filter() and map() creates a hidden intermediate iterator for each step. A transducer collapses the entire transformation stack into one reducing step, eliminating all intermediate allocations. This is how high-performance data pipelines in Clojure and similar languages work, and understanding it deepens your intuition for how lazy pipelines and stream processing operate.

Given Input: numbers = list(range(1, 11))

Expected Output: [4, 16, 36, 64, 100]

▼ Hint
  • A transducer is a function that takes a reducing function and returns a new reducing function. Start by writing a mapping(transform) transducer that wraps a reducer: lambda reducer: lambda acc, x: reducer(acc, transform(x)).
  • Write a filtering(predicate) transducer similarly: lambda reducer: lambda acc, x: reducer(acc, x) if predicate(x) else acc.
  • Compose the two transducers by calling one with the result of the other, then apply the composed reducer via reduce(composed_reducer, numbers, []). The base reducer appends to the accumulator list.
▼ Solution & Explanation
from functools import reduce

# Base reducer: appends a value to the accumulator list
append = lambda acc, x: acc + [x]

# Transducer factories
def mapping(transform):
    return lambda reducer: lambda acc, x: reducer(acc, transform(x))

def filtering(predicate):
    return lambda reducer: lambda acc, x: reducer(acc, x) if predicate(x) else acc

# Compose: filter evens, then square
xf = filtering(lambda n: n % 2 == 0)(
         mapping(lambda n: n ** 2)(append)
     )

numbers = list(range(1, 11))
result = reduce(xf, numbers, [])

print(result)   # [4, 16, 36, 64, 100]Code language: Python (python)

Explanation:

  • What a transducer is: A transducer is a higher-order function with the signature reducer -> reducer. It transforms a reducing function into a new one that applies an extra step (a map or filter operation) before delegating to the inner reducer. Composing two transducers stacks those steps without ever materialising an intermediate collection.
  • Composition order: filtering(...)(mapping(...)(append)) reads inside-out. The innermost call wraps append with the squaring step, producing a reducer that squares and then appends. The outer call wraps that with the even-check step: only even numbers reach the squaring reducer. The overall data flow is: filter first, square second, append third.
  • Single-pass guarantee: reduce(xf, numbers, []) iterates over numbers exactly once. For each element, xf decides whether to include it and how to transform it in a single function call chain. No intermediate list is created between the filtering and mapping steps.
  • When this matters: For small lists the performance difference is negligible. For pipelines processing millions of records or infinite streams, eliminating intermediate allocations can reduce memory usage dramatically. The transducer pattern also makes the transformation stack inspectable and reusable across different collection types.

Exercise 18: Lazy Evaluation with Generators

Problem Statement: Define an infinite sequence of natural numbers using a generator function. Use generator expressions (not map() or filter()) to lazily filter out odd numbers and square the remaining evens. Take only the first 5 results using itertools.islice().

Purpose: An infinite sequence cannot be fully evaluated — it must be processed lazily. Generator expressions create lazy pipelines that compute each value only when it is requested, using constant memory regardless of how long the sequence is. This exercise shows how to build such pipelines and how itertools.islice() acts as the valve that controls how many values are pulled through.

Given Input: An infinite stream of natural numbers starting from 1.

Expected Output: [4, 16, 36, 64, 100]

▼ Hint
  • Write a generator naturals() using while True to yield an unbounded sequence: yield n, then increment n. This generator never raises StopIteration on its own.
  • Layer generator expressions on top: evens = (n for n in naturals() if n % 2 == 0), then squared = (n ** 2 for n in evens). Each expression is a lazy wrapper — no values are computed yet.
  • Use list(itertools.islice(squared, 5)) to pull exactly 5 values through the entire pipeline and stop.
▼ Solution & Explanation
import itertools

def naturals():
    n = 1
    while True:
        yield n
        n += 1

# Lazy pipeline using generator expressions
evens   = (n      for n in naturals() if n % 2 == 0)
squared = (n ** 2 for n in evens)

result = list(itertools.islice(squared, 5))
print(result)   # [4, 16, 36, 64, 100]Code language: Python (python)

Explanation:

  • Infinite generator: naturals() uses while True combined with yield to produce an unbounded stream. Control returns to the caller after each yield and resumes at the next line when next() is called again. The generator itself never finishes — it relies on the consumer to stop pulling values.
  • Layered generator expressions: Each generator expression wraps the previous one without consuming it. Writing (n ** 2 for n in evens) creates a new generator object that, when iterated, pulls from evens, which pulls from naturals(). The entire chain is wired up but idle until a value is requested.
  • itertools.islice(iterable, n): Takes at most n items from any iterable and then stops. It is the standard tool for safely consuming a finite prefix of an infinite stream. Without it, calling list(squared) would loop forever.
  • Memory usage: At any point in time, only one value exists in memory as it flows through the pipeline. Contrast this with a list-based approach where filter() and map() over a large pre-built list would require storing all intermediate values. Generator pipelines scale to arbitrarily large or infinite data sources with O(1) memory overhead.

Exercise 19: Higher-Order Function Library

Problem Statement: Implement your own versions of my_map(func, lst), my_filter(pred, lst), and my_reduce(func, lst, initial) from scratch using only recursion and lambdas. No loops, no built-in map/filter/reduce, and no list comprehensions. Verify each against a known input.

Purpose: Rebuilding standard library functions from first principles is one of the most effective ways to understand how they work. This exercise makes explicit the recursive structure hidden inside every functional operation: process the head of the list, then recurse on the tail until the base case (an empty list) is reached.

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

Expected Output:

my_map    (square, numbers)      = [1, 4, 9, 16, 25]
my_filter (is_even, numbers)     = [2, 4]
my_reduce (multiply, numbers, 1) = 120
▼ Hint
  • Each function has the same recursive skeleton: base case returns a neutral value when lst is empty; recursive case processes lst[0] (the head) and concatenates or combines it with the result of calling the function again on lst[1:] (the tail).
  • For my_map: return [] when the list is empty; otherwise return [func(lst[0])] + my_map(func, lst[1:]).
  • For my_filter: include lst[0] in the result only when pred(lst[0]) is true; always recurse on lst[1:].
  • For my_reduce: the accumulator starts as initial; on each recursive call pass func(initial, lst[0]) as the new accumulator.
▼ Solution & Explanation
def my_map(func, lst):
    if not lst:
        return []
    return [func(lst[0])] + my_map(func, lst[1:])

def my_filter(pred, lst):
    if not lst:
        return []
    head = lst[0]
    rest = my_filter(pred, lst[1:])
    return [head] + rest if pred(head) else rest

def my_reduce(func, lst, initial):
    if not lst:
        return initial
    return my_reduce(func, lst[1:], func(initial, lst[0]))

# Test callables
square   = lambda x: x ** 2
is_even  = lambda x: x % 2 == 0
multiply = lambda acc, x: acc * x

numbers = [1, 2, 3, 4, 5]

print(f"my_map    (square, numbers)      = {my_map(square, numbers)}")
print(f"my_filter (is_even, numbers)     = {my_filter(is_even, numbers)}")
print(f"my_reduce (multiply, numbers, 1) = {my_reduce(multiply, numbers, 1)}")Code language: Python (python)

Explanation:

  • Recursive structure: Each function follows the same two-branch pattern. The base case (if not lst: return [] or return initial) terminates the recursion when the list is exhausted. The recursive case processes the head element and delegates the rest of the work to the same function called on the tail, one element shorter each time.
  • my_map – transform and prepend: [func(lst[0])] + my_map(func, lst[1:]) applies func to the first element, wraps the result in a single-element list, and concatenates it with the recursively mapped tail. The final result is assembled as the call stack unwinds.
  • my_filter – conditional inclusion: The ternary expression [head] + rest if pred(head) else rest includes the head only when the predicate is true. Either way, the recursion continues on the tail so all elements are visited.
  • my_reduce – tail-recursive accumulation: The accumulator is updated at each call rather than on the way back up, making this tail-recursive in structure. Python does not optimise tail calls, so a list of more than a few thousand elements would hit the default recursion limit. In production, use the built-in functools.reduce() which uses an iterative implementation internally.

Exercise 20: Monadic Chaining

Problem Statement: Implement a Maybe class that wraps a value (or None) and provides a .bind(func) method. bind applies func to the wrapped value if it is not None, wrapping the result in a new Maybe; if the value is None, it returns Maybe(None) immediately without calling func. Chain several lambda transformations and show that a None at any step short-circuits the rest of the chain.

Purpose: The Maybe monad is the functional programming solution to the problem of None-propagation. Instead of sprinkling if x is not None: guards throughout your code, you chain transformations and let the monad handle the short-circuit logic invisibly. This pattern appears in Haskell as Maybe, in Scala as Option, and in Python libraries such as returns and pymonad.

Given Input: Two chains starting from Maybe(10) and Maybe(None), each passing through the same three lambda transformations.

Expected Output:

Maybe(10).bind(double).bind(add_three).bind(safe_sqrt) = Maybe(4.69...)
Maybe(None).bind(double).bind(add_three).bind(safe_sqrt) = Maybe(None)
▼ Hint
  • Store the value in self.value inside __init__. Give the class a __repr__ that returns f"Maybe({self.value})" for clear output.
  • In bind(self, func): if self.value is None, return Maybe(None) immediately. Otherwise return Maybe(func(self.value)).
  • For the safe_sqrt transformation, return None for negative inputs so you can demonstrate a mid-chain short-circuit as well.
▼ Solution & Explanation
import math

class Maybe:
    def __init__(self, value):
        self.value = value

    def bind(self, func):
        if self.value is None:
            return Maybe(None)
        return Maybe(func(self.value))

    def __repr__(self):
        return f"Maybe({self.value})"

# Transformation lambdas
double    = lambda x: x * 2
add_three = lambda x: x + 3
safe_sqrt = lambda x: math.sqrt(x) if x >= 0 else None

# Chain starting from a real value
result_a = (Maybe(10)
            .bind(double)
            .bind(add_three)
            .bind(safe_sqrt))
print(result_a)   # Maybe(4.69...)

# Chain starting from None - all steps are skipped
result_b = (Maybe(None)
            .bind(double)
            .bind(add_three)
            .bind(safe_sqrt))
print(result_b)   # Maybe(None)

# Mid-chain short-circuit: safe_sqrt returns None for negatives
result_c = (Maybe(-4)
            .bind(double)       # -8
            .bind(safe_sqrt)    # None (negative input)
            .bind(add_three))   # skipped
print(result_c)   # Maybe(None)Code language: Python (python)

Explanation:

  • bind(self, func): This is the core monad operation (called flatMap or >>= in other languages). It checks the wrapped value before applying the function. If the value is None, the function is never called and the None propagates. If the value is present, the function runs and its result is wrapped in a new Maybe.
  • Short-circuit propagation: Once None appears anywhere in the chain, every subsequent bind call returns Maybe(None) immediately without executing the lambda. The entire chain runs to completion without raising exceptions or requiring any conditional checks in the calling code.
  • Returning None from a lambda: When safe_sqrt returns None for a negative input, bind wraps that None in a Maybe. The next bind sees self.value is None and short-circuits. This demonstrates that the monad handles failures originating anywhere in the chain, not just at the start.
  • Comparison to exception handling: The Maybe monad and try/except both deal with operations that can fail, but in different styles. Exceptions use control flow that jumps out of the normal execution path. Maybe keeps everything in a linear chain and encodes the failure as data inside the wrapper. The monadic approach is easier to compose and reason about when the entire pipeline is built from pure functions.

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

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

Leave a Reply Cancel reply

your email address will NOT be published. all comments are moderated according to our comment policy.

Use <pre> tag for posting code. E.g. <pre> Your entire code </pre>

In: Python Python Exercises
TweetF  sharein  shareP  Pin

  Python Exercises

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

 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