Python Recursion

Python Tutorial › Functions › Recursion

Python Recursion

IntermediateLesson 27 of 4710 min read10 runnable examples

A recursive function is a function that calls itself to solve a smaller version of the same problem. Recursion is natural for problems with a repeating structure, such as nested folders, trees and many classic interview questions.

In this lesson
  • The base case and the recursive case
  • Trace how recursive calls unwind
  • Avoid the recursion limit
  • Speed up recursion with memoization

How recursion works

Every recursive function needs two parts:

  1. A base case that answers directly and stops the recursion.
  2. A recursive case that calls the function on a smaller input.
Python
def countdown(n):    if n == 0:              # base case        print("Lift off!")        return    print(n)    countdown(n - 1)        # recursive case: a smaller problem countdown(3)
Output
3
2
1
Lift off!

Factorial

5! means 5 × 4 × 3 × 2 × 1. Notice that 5! = 5 × 4!, which is a recursive definition.

Python
def factorial(n):    if n <= 1:        return 1    return n * factorial(n - 1) print(factorial(5))print(factorial(20))
Output
120
2432902008176640000

Trace the calls

Indenting each call by its depth shows how the calls go down to the base case and the results come back up:

Python
def factorial(n, depth=0):    pad = "  " * depth    print(f"{pad}factorial({n})")    if n <= 1:        result = 1    else:        result = n * factorial(n - 1, depth + 1)    print(f"{pad}-> {result}")    return result factorial(4)
Output
factorial(4)
  factorial(3)
    factorial(2)
      factorial(1)
      -> 1
    -> 2
  -> 6
-> 24

Sum of digits and reversing a string

Python
def digit_sum(n):    if n < 10:        return n    return n % 10 + digit_sum(n // 10) def reverse(text):    if len(text) <= 1:        return text    return reverse(text[1:]) + text[0] print(digit_sum(40721), reverse("python"))
Output
14 nohtyp

Recursion on nested data

Recursion shines when data contains smaller copies of itself, such as lists inside lists:

Python
def flatten(items):    flat = []    for item in items:        if isinstance(item, list):            flat.extend(flatten(item))     # recurse into the sub-list        else:            flat.append(item)    return flat print(flatten([1, [2, [3, 4], 5], [[6]]]))
Output
[1, 2, 3, 4, 5, 6]

The recursion limit

Each call uses memory on the call stack. Python stops runaway recursion with a RecursionError (the default limit is about 1000 calls). A missing base case causes it immediately:

Python
def forever(n):    return forever(n + 1) forever(0)
Output
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    forever(0)
    ~~~~~~~^^^
  File "main.py", line 2, in forever
    return forever(n + 1)
  File "main.py", line 2, in forever
    return forever(n + 1)
  File "main.py", line 2, in forever
    return forever(n + 1)
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
Python
import sysprint(sys.getrecursionlimit())
Output
1000
Watch out

Do not just raise the limit with sys.setrecursionlimit() to make a deep recursion work. Rewrite it as a loop instead.

Fibonacci and memoization

The naive Fibonacci function recalculates the same values again and again, so it becomes very slow:

Python
calls = 0def fib(n):    global calls    calls += 1    return n if n < 2 else fib(n - 1) + fib(n - 2) print(fib(20), "calls:", calls)
Output
6765 calls: 21891

functools.lru_cache remembers results, so each value is computed only once:

Python
from functools import lru_cache @lru_cache(maxsize=None)def fib(n):    return n if n < 2 else fib(n - 1) + fib(n - 2) print(fib(80))print(fib.cache_info().misses, "distinct calls")
Output
23416728348467685
81 distinct calls

Recursion or a loop?

Python
def factorial_loop(n):    result = 1    for i in range(2, n + 1):        result *= i    return result print(factorial_loop(5), factorial_loop(5) == 120)
Output
120 True
RecursionLoop
Best forTrees, nested data, divide-and-conquerSimple repetition
ReadabilityMatches recursive definitionsMatches step-by-step thinking
LimitsRecursion depth (about 1000)None
Speed in PythonSlower (function call overhead)Faster

Check your understanding

Predict the output

def power(base, exp):    if exp == 0:        return 1    return base * power(base, exp - 1) print(power(2, 10), power(5, 0))

Quick quiz

1. What stops a recursive function from calling itself forever?

2. What error does Python raise for recursion that is too deep?

3. What does @lru_cache do for a recursive function?

Exercise: count files in nested folders

A folder is a dict mapping names to either a file size (int) or another folder (dict). Write a recursive function that returns the total size.

Input
{"a.txt": 10, "img": {"b.png": 200, "old": {"c.png": 50}}, "d.md": 5}
Expected output
265
Hint 1

If a value is a dict, call the function on it; otherwise add the number.

Program Practice

Python recursion FAQ

What is recursion in Python?

Recursion is when a function calls itself to solve a smaller instance of the same problem, until it reaches a base case that it can answer directly.

What is the maximum recursion depth in Python?

The default limit is 1000 frames. sys.getrecursionlimit() shows it. Deep problems should use a loop instead.

Is recursion faster than iteration in Python?

No. Python function calls are relatively expensive, so a loop is usually faster. Use recursion when it makes the code clearer.

Python Scope

Python Tutorial › Functions › Scope

Python Scope

IntermediateLesson 26 of 479 min read8 runnable examples

Scope decides where a name can be seen. A variable created inside a function exists only inside that function, while a variable created at the top of a file can be read everywhere. Understanding scope explains several confusing errors.

In this lesson
  • Local and global variables
  • The LEGB lookup rule
  • When to use global and nonlocal
  • Closures that remember values

Local scope

A variable assigned inside a function is local: it is created when the function runs and disappears when it returns.

Python
def make_report():    title = "Daily report"     # local    print(title) make_report()print(title)
Output
Daily report
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    print(title)
          ^^^^^
NameError: name 'title' is not defined. Did you mean: 'tuple'?

Global scope

A variable assigned at the top level of a file is global. Functions can read it.

Python
BASE_URL = "https://sqatools.in" def page_url(path):    return BASE_URL + path      # reading a global is fine print(page_url("/python-tutorials/"))
Output
https://sqatools.in/python-tutorials/

Same name, different variables

Assigning inside a function creates a new local variable, even if a global has the same name. The global is untouched.

Python
count = 10 def reset():    count = 0                   # a new local count    print("inside:", count) reset()print("outside:", count)
Output
inside: 0
outside: 10

The LEGB rule

When Python looks up a name, it searches four scopes in order and uses the first match:

OrderScopeExample
LLocal: the current functionvariables assigned in the function
EEnclosing: outer functionsvariables of a function that contains this one
GGlobal: the moduletop-level variables and functions
BBuilt-inprint, len, max
Python
x = "global" def outer():    x = "enclosing"    def inner():        print(x)          # not local, found in the enclosing function    inner() outer()print(len("built-in lookup"))
Output
enclosing
15

The global keyword

To assign to a global variable inside a function, declare it with global:

Python
counter = 0 def increment():    global counter    counter += 1 increment()increment()print(counter)
Output
2
Watch out

Functions that change globals are hard to test and reason about. Prefer passing values in and returning results: counter = increment(counter).

UnboundLocalError

If a function assigns to a name anywhere, that name is local for the whole function. Reading it before the assignment fails:

Python
total = 100 def add_tax():    total = total * 1.18      # total is local here, but has no value yet    return total add_tax()
Output
Traceback (most recent call last):
  File "main.py", line 7, in <module>
    add_tax()
    ~~~~~~~^^
  File "main.py", line 4, in add_tax
    total = total * 1.18      # total is local here, but has no value yet
            ^^^^^
UnboundLocalError: cannot access local variable 'total' where it is not associated with a value

The fix is to pass the value in: def add_tax(total): return total * 1.18.

nonlocal and closures

nonlocal lets an inner function assign to a variable of its enclosing function. The inner function keeps that variable alive even after the outer function has returned. This is called a closure.

Python
def make_counter():    count = 0    def next_value():        nonlocal count        count += 1        return count    return next_value ticket = make_counter()print(ticket(), ticket(), ticket()) other = make_counter()        # a separate countprint(other())
Output
1 2 3
1

Closures are useful for creating customised functions:

Python
def make_multiplier(factor):    def multiply(x):        return x * factor    return multiply double = make_multiplier(2)triple = make_multiplier(3)print(double(5), triple(5))
Output
10 15

Check your understanding

Predict the output

x = 1def f():    x = 2    def g():        nonlocal x        x = 3    g()    return xprint(f(), x)

Quick quiz

1. In what order does Python look up a name?

2. What is needed to change a module-level variable inside a function?

3. Why does UnboundLocalError happen?

Exercise: running average

Write make_averager() that returns a function. Each call adds a number and returns the average so far.

Input
10, then 20, then 60
Expected output
10.0 15.0 30.0
Hint 1

Keep total and count in the outer function and use nonlocal.

Program Practice

Python scope FAQ

What is the difference between local and global variables in Python?

A local variable is created inside a function and exists only while it runs. A global variable is created at the top level of a module and can be read anywhere in it.

When should I use the global keyword?

Rarely. Use it only when a function really must rebind a module-level variable. Passing values in and returning results is usually clearer.

What does nonlocal do in Python?

It lets a nested function assign to a variable of the enclosing function instead of creating a new local one.

Python Lambda

Python Tutorial › Functions › Lambda

Python Lambda

IntermediateLesson 25 of 478 min read10 runnable examples

A lambda is a small function written in a single expression, without a name. You will mostly use lambdas as short “key” or “rule” functions passed to sorted(), max(), map() and filter().

In this lesson
  • Write a lambda expression
  • Sort data with a lambda key
  • Transform and filter with map() and filter()
  • Know when a normal def is better

Lambda syntax

lambda parameters: expression. The expression’s value is returned automatically; there is no return keyword.

Python
square = lambda x: x * xadd = lambda a, b: a + b print(square(6), add(2, 3))
Output
36 5

The two lines above are equivalent to:

Python
def square(x):    return x * x print(square(6))
Output
36
Note

Assigning a lambda to a name, as in the first example, works but PEP 8 recommends def for that. Lambdas are meant to be used inline, where a function is needed only once.

Sort with a lambda key

key tells sorted() what to compare. The lambda receives each item and returns the value to sort by.

Python
employees = [("Asha", 52000), ("Ravi", 61000), ("Meena", 48000)] by_salary = sorted(employees, key=lambda e: e[1])print(by_salary) by_name_desc = sorted(employees, key=lambda e: e[0], reverse=True)print([name for name, _ in by_name_desc])
Output
[('Meena', 48000), ('Asha', 52000), ('Ravi', 61000)]
['Ravi', 'Meena', 'Asha']

Sort dictionaries, or sort by several things at once by returning a tuple:

Python
users = [{"name": "ravi", "age": 31}, {"name": "asha", "age": 28}, {"name": "meena", "age": 31}]print([u["name"] for u in sorted(users, key=lambda u: (-u["age"], u["name"]))])
Output
['meena', 'ravi', 'asha']

max() and min() with a key

Python
words = ["python", "is", "readable"]print(max(words, key=lambda w: len(w))) prices = {"pen": 10, "bag": 650, "book": 120}print(min(prices, key=lambda k: prices[k]))
Output
readable
pen

map(): transform every item

map(function, iterable) applies the function to each item and returns an iterator. Wrap it in list() to see the results.

Python
celsius = [0, 25, 37.5, 100]fahrenheit = list(map(lambda c: c * 9 / 5 + 32, celsius))print(fahrenheit) print(list(map(str.upper, ["get", "post"])))   # any function works, not just lambdas
Output
[32.0, 77.0, 99.5, 212.0]
['GET', 'POST']

filter(): keep matching items

filter(function, iterable) keeps the items for which the function returns a truthy value.

Python
scores = [45, 82, 67, 91, 38]print(list(filter(lambda s: s >= 50, scores)))
Output
[82, 67, 91]
Tip

A list comprehension is often clearer than map or filter with a lambda: [c * 9 / 5 + 32 for c in celsius] and [s for s in scores if s >= 50] do the same jobs. Use whichever reads better.

Lambdas with default values

Python
apply_discount = lambda price, pct=10: round(price * (1 - pct / 100), 2)print(apply_discount(500), apply_discount(500, 25))
Output
450.0 375.0

Limits of lambda

A lambda can contain only one expression: no statements, no if blocks (a conditional expression is fine), no loops, no assignments.

Python
grade = lambda m: "pass" if m >= 50 else "fail"print(grade(72), grade(31))
Output
pass fail
Python
bad = lambda x: y = x + 1
Output
  File "main.py", line 1
    bad = lambda x: y = x + 1
          ^^^^^^^^^^^
SyntaxError: cannot assign to lambda

If the logic needs more than one line, write a normal def.

Check your understanding

Predict the output

pairs = [(1, "b"), (3, "a"), (2, "c")]print(sorted(pairs, key=lambda p: p[1]), list(map(lambda p: p[0] * 10, pairs)))

Quick quiz

1. What does (lambda x, y: x * y)(3, 4) return?

2. Which is NOT allowed in a lambda?

3. What does map() return in Python 3?

Exercise: sort products

Sort the products by price, cheapest first, then print the names of products costing more than 100 using filter.

Input
[("pen", 10), ("bag", 650), ("book", 120), ("tape", 35)]
Expected output
[('pen', 10), ('tape', 35), ('book', 120), ('bag', 650)]
['bag', 'book']
Hint 1

key=lambda p: p[1] sorts by price.

Program Practice

Python lambda FAQ

What is a lambda function in Python?

A lambda is an anonymous function written as a single expression: lambda x: x * 2. It is used for short functions passed to other functions.

Can a lambda have multiple lines?

No. A lambda holds exactly one expression. Use def for anything longer.

When should I use lambda instead of def?

Use a lambda for a short throwaway function passed as an argument, such as a key for sorted(). Use def when the function is reused, needs a name, or has more than one step.

Python List Comprehension

Python Tutorial › Data Structures › List Comprehension

Python List Comprehension

IntermediateLesson 19 of 4710 min read8 runnable examples

A list comprehension builds a new list from an existing one in a single, readable line. It replaces the common “empty list, loop, append” pattern and is one of the features that makes Python code short.

In this lesson
  • Turn a for loop into a comprehension
  • Filter items with if
  • Choose values with if-else
  • Build sets and dicts the same way

From loop to comprehension

Here is the familiar pattern: start with an empty list, loop, and append.

Python
numbers = [1, 2, 3, 4, 5]squares = []for n in numbers:    squares.append(n * n)print(squares)
Output
[1, 4, 9, 16, 25]

The comprehension says the same thing in one line. Read it as “n times n, for each n in numbers“:

Python
numbers = [1, 2, 3, 4, 5]squares = [n * n for n in numbers]print(squares)
Output
[1, 4, 9, 16, 25]

The general form is [expression for item in iterable].

Filter with if

Add if condition at the end to keep only some items.

Python
scores = [45, 82, 67, 91, 38, 74]passed = [s for s in scores if s >= 50]print(passed) words = ["api", "", "ui", "  ", "db"]clean = [w for w in words if w.strip()]print(clean)
Output
[82, 67, 91, 74]
['api', 'ui', 'db']

Choose a value with if-else

To transform every item one of two ways, put a conditional expression at the front. Note the different position from the filter above.

Python
scores = [45, 82, 67, 91, 38]labels = ["pass" if s >= 50 else "fail" for s in scores]print(labels)
Output
['fail', 'pass', 'pass', 'pass', 'fail']
Tip

if at the end filters (fewer items). if ... else at the front maps (same number of items). You can use both: [x * 2 if x > 0 else 0 for x in data if x is not None].

Work with strings

Python
sentence = "Python makes testing simple"print([w.upper() for w in sentence.split()])print([len(w) for w in sentence.split()])print([ch for ch in "a1b2c3" if ch.isdigit()])
Output
['PYTHON', 'MAKES', 'TESTING', 'SIMPLE']
[6, 5, 7, 6]
['1', '2', '3']

Nested comprehensions

Two for clauses work like nested loops, read from left to right:

Python
pairs = [(x, y) for x in [1, 2, 3] for y in "ab"]print(pairs) matrix = [[1, 2, 3], [4, 5, 6]]flat = [n for row in matrix for n in row]print(flat) transposed = [[row[i] for row in matrix] for i in range(3)]print(transposed)
Output
[(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'b')]
[1, 2, 3, 4, 5, 6]
[[1, 4], [2, 5], [3, 6]]
Watch out

If a comprehension needs more than two for clauses or a long condition, write a normal loop. Readability counts more than saving lines.

Set and dict comprehensions

Use { } to build a set, and {key: value ...} to build a dict.

Python
emails = ["a@x.com", "b@y.com", "c@x.com"]domains = {e.split("@")[1] for e in emails}print(sorted(domains)) prices = {"pen": 10, "book": 120, "bag": 650}with_tax = {item: round(p * 1.18, 2) for item, p in prices.items()}print(with_tax) expensive = {k: v for k, v in prices.items() if v > 100}print(expensive)
Output
['x.com', 'y.com']
{'pen': 11.8, 'book': 141.6, 'bag': 767.0}
{'book': 120, 'bag': 650}

Generator expressions

With round brackets you get a generator expression. It produces values one at a time instead of building a list, which saves memory when you only need to consume the values once, for example in sum():

Python
print(sum(n * n for n in range(1, 101)))print(any(s < 40 for s in [45, 82, 38]))
Output
338350
True

The Generators lesson explains how this works.

Check your understanding

Predict the output

data = [3, -1, 0, 8, -5]print([abs(x) for x in data if x != 0], [x if x > 0 else 0 for x in data])

Quick quiz

1. What does [x * 2 for x in range(3)] produce?

2. Where does a filtering if go?

3. Which builds a set?

Exercise: even squares and word lengths

Build a list of the squares of the even numbers from 1 to 10, and a dict mapping each word to its length.

Input
range 1 to 10; words = ["login", "cart", "checkout"]
Expected output
[4, 16, 36, 64, 100]
{'login': 5, 'cart': 4, 'checkout': 8}
Hint 1

Filter with if n % 2 == 0.

Hint 2

A dict comprehension looks like {w: len(w) for w in words}.

Program Practice

Many list programs can be solved in one line with a comprehension. Solve each one with a loop first, then rewrite it.

Python list comprehension FAQ

Is list comprehension faster than a for loop?

Usually a little, because the append happens inside the interpreter. Choose it for readability first; the speed gain is small.

Can I use else in a list comprehension?

Yes, as part of a conditional expression at the front: [a if cond else b for x in items]. A filtering if at the end cannot have an else.

What is the difference between a list comprehension and a generator expression?

A list comprehension [...] builds the whole list in memory. A generator expression (...) yields items one at a time, which is better for large data that you only loop over once.

Python String Formatting & f-strings

Python Tutorial › Strings › String Formatting & f-strings

Python String Formatting & f-strings

BeginnerLesson 17 of 4710 min read8 runnable examples

Formatting turns values into neat, readable text: prices with two decimals, columns that line up, percentages, dates. f-strings are the modern way to do it, and the format codes you learn here also work with str.format().

In this lesson
  • Put values into text with f-strings
  • Control decimals, width and alignment
  • Format numbers, percentages and dates
  • Recognise format() and % formatting in older code

f-string basics

Put an f before the opening quote and write any expression inside { }.

Python
name = "Asha"tests, passed = 40, 37 print(f"{name} ran {tests} tests")print(f"Pass rate: {passed / tests}")print(f"Upper: {name.upper()}, failed: {tests - passed}")
Output
Asha ran 40 tests
Pass rate: 0.925
Upper: ASHA, failed: 3

Format specifiers

After the expression, a colon starts the format specifier: {value:spec}.

Python
pi = 3.14159265print(f"{pi:.2f}")        # 2 decimal placesprint(f"{pi:.4f}")print(f"{1234567.891:,.2f}")   # thousands separatorprint(f"{0.8125:.1%}")    # percentageprint(f"{42:05d}")        # pad with zeros to width 5print(f"{255:x} {255:b}") # hex and binary
Output
3.14
3.1416
1,234,567.89
81.2%
00042
ff 11111111
SpecMeaningExampleResult
.2ffixed-point, 2 decimalsf"{3.14159:.2f}"3.14
,thousands separatorf"{1500000:,}"1,500,000
.1%percentage, 1 decimalf"{0.256:.1%}"25.6%
05dzero-pad to width 5f"{42:05d}"00042
>10right-align in 10 charsf"{'hi':>10}" hi
<10 / ^10left / centref"{'hi':^10}" hi
escientificf"{12345.678:.2e}"1.23e+04

Width and alignment

Fixed widths make columns line up. < aligns left, > right and ^ centre; the character before the arrow is the fill.

Python
rows = [("Keyboard", 2, 1499.5), ("Mouse", 10, 349), ("Monitor", 1, 12999)] print(f"{'Item':<10}{'Qty':>5}{'Price':>12}")print("-" * 27)for item, qty, price in rows:    print(f"{item:<10}{qty:>5}{price:>12,.2f}")print(f"{' Total ':=^27}")
Output
Item        Qty       Price
---------------------------
Keyboard      2    1,499.50
Mouse        10      349.00
Monitor       1   12,999.00
========== Total ==========

Debugging with =

Adding = after the expression prints the expression and its value:

Python
user_id = 7retries = 3print(f"{user_id=}, {retries * 2=}")
Output
user_id=7, retries * 2=6

Dates in f-strings

Date objects understand their own format codes (covered fully in the Datetime lesson):

Python
from datetime import date d = date(2026, 11, 2)print(f"{d:%d %B %Y}")print(f"{d:%Y-%m-%d} is a {d:%A}")
Output
02 November 2026
2026-11-02 is a Monday

Braces and quotes inside f-strings

Double the braces to print them literally. Use a different quote style for strings inside the expression:

Python
key = "id"print(f"{{{key}}}")data = {"id": 7}print(f"value: {data['id']}")
Output
{id}
value: 7

str.format()

str.format() uses the same {} placeholders and format specs. You will see it in older code and when a template is stored in a variable:

Python
template = "{name} scored {score:.1f}%"print(template.format(name="Ravi", score=87.456))print("{0} vs {1}, {0} wins".format("India", "Australia"))
Output
Ravi scored 87.5%
India vs Australia, India wins

The old % style

The oldest style uses % codes. It still appears in logging calls and old scripts:

Python
print("%s has %d items costing %.2f" % ("cart", 3, 499.999))
Output
cart has 3 items costing 500.00
Tip

Use f-strings for new code. They are the fastest and the easiest to read. Use str.format() only when the template must be defined before the values exist.

Check your understanding

Predict the output

x = 7.0 / 3print(f"[{x:.3f}] [{x:8.2f}] [{'ok':*^6}] [{1e6:,.0f}]")

Quick quiz

1. Which prints 0.50 for x = 0.5?

2. What does f"{0.25:.0%}" produce?

3. How do you print a literal { in an f-string?

Exercise: invoice line

Print each line as a 12-character left-aligned item name, the quantity right-aligned in 4 characters, and the line total with a thousands separator and 2 decimals in 12 characters.

Input
[("Laptop", 2, 54999), ("USB cable", 5, 199.5)]
Expected output
Laptop         2  109,998.00
USB cable      5      997.50
Hint 1

The line total is qty * price; use the spec >12,.2f.

Program Practice

Python string formatting FAQ

How do I format a float to 2 decimal places in Python?

Use an f-string: f"{value:.2f}". It returns a string; use round(value, 2) if you need a number.

What is an f-string in Python?

An f-string is a string literal prefixed with f in which {expression} placeholders are replaced by their values. They were added in Python 3.6.

How do I add commas to large numbers in Python?

Use the , format option: f"{1234567:,}" gives 1,234,567.

Which is better: f-string, format() or %?

f-strings are the most readable and the fastest. str.format() is useful for reusable templates, and % formatting is mainly found in older code and logging.

Python Match Case

Python Tutorial › Control Flow › Match Case

Python Match Case

BeginnerLesson 14 of 4710 min read6 runnable examples

match compares one value against a list of patterns and runs the block for the first pattern that fits. It is Python’s answer to the switch statement found in other languages, and it can also pull values out of lists and dictionaries.

In this lesson
  • Replace long elif chains with match
  • Combine values with OR patterns
  • Add conditions with guards
  • Match and unpack lists and dicts

Your first match statement

match was added in Python 3.10. Each case is a pattern. _ is the wildcard: it matches anything, so it works like else.

Python
status_code = 404 match status_code:    case 200:        print("OK")    case 404:        print("Not found")    case 500:        print("Server error")    case _:        print("Unknown status")
Output
Not found

Only the first matching case runs. Unlike C or Java, there is no fall-through and no break needed.

Several values in one case

Use | to mean “or”:

Python
def day_type(day):    match day.lower():        case "saturday" | "sunday":            return "weekend"        case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":            return "weekday"        case _:            return "not a day" for d in ["Sunday", "wednesday", "Funday"]:    print(d, "->", day_type(d))
Output
Sunday -> weekend
wednesday -> weekday
Funday -> not a day

Capture a value and add a guard

A bare name in a pattern captures the value. An if after the pattern is a guard: the case matches only if the guard is true.

Python
def grade(score):    match score:        case n if n >= 90:            return "A"        case n if n >= 75:            return "B"        case n if n >= 0:            return "C or below"        case _:            return "invalid" print(grade(95), grade(80), grade(40), grade(-5))
Output
A B C or below invalid
Tip

For simple range checks like this, if / elif is just as clear. match really shines when you need to look at the shape of the data, as below.

Match sequences

Patterns can describe lists and tuples. Names in the pattern are filled from the matching positions, and *rest collects the remaining items.

Python
def run(command):    match command.split():        case ["quit"]:            return "Bye"        case ["go", direction]:            return f"Going {direction}"        case ["take", first, *others]:            return f"Taking {first} and {len(others)} more"        case []:            return "Say something"        case _:            return f"Unknown command: {command!r}" for c in ["go north", "take key lamp rope", "quit", "", "dance"]:    print(run(c))
Output
Going north
Taking key and 2 more
Bye
Say something
Unknown command: 'dance'

Match dictionaries

A mapping pattern checks that certain keys exist and captures their values. Extra keys are ignored, which makes this ideal for JSON from an API.

Python
events = [    {"type": "click", "x": 10, "y": 20},    {"type": "key", "key": "Enter"},    {"type": "scroll"},] for event in events:    match event:        case {"type": "click", "x": x, "y": y}:            print(f"click at ({x}, {y})")        case {"type": "key", "key": key}:            print(f"key {key} pressed")        case {"type": other}:            print(f"unhandled event {other}")
Output
click at (10, 20)
key Enter pressed
unhandled event scroll

Match by type

int(), str() and other class names in a pattern check the type:

Python
def describe(value):    match value:        case bool():            return "a boolean"        case int() | float():            return "a number"        case str() if value == "":            return "an empty string"        case str():            return "a string"        case list():            return f"a list of {len(value)}"        case _:            return "something else" for v in [True, 3, 2.5, "", "hi", [1, 2], None]:    print(repr(v), "is", describe(v))
Output
True is a boolean
3 is a number
2.5 is a number
'' is an empty string
'hi' is a string
[1, 2] is a list of 2
None is something else
Note

bool() is checked before int() because True is also an int. As with elif, put the more specific case first.

Check your understanding

Predict the output

point = (0, 5)match point:    case (0, 0):        print("origin")    case (0, y):        print(f"on the y axis at {y}")    case (x, 0):        print(f"on the x axis at {x}")    case _:        print("somewhere else")

Quick quiz

1. Which Python version introduced match?

2. What does case _: do?

3. How do you match “y” or “yes” in one case?

Exercise: calculator

Write calc(a, op, b) with match that supports +, -, * and /, returns “cannot divide by zero” for division by 0, and “unknown operator” otherwise.

Input
calc(6, "*", 7), calc(9, "/", 0), calc(9, "/", 3), calc(1, "^", 2)
Expected output
42 cannot divide by zero 3.0 unknown operator
Hint 1

A guard like case "/" if b == 0: must come before case "/":.

Program Practice

The if-else programs are a great place to practise match: try rewriting a few solutions with it.

Python match case FAQ

Does Python have a switch case statement?

Yes, since Python 3.10 the match statement does everything a switch does, plus matching on the structure of lists, tuples, dicts and objects.

Do I need break in Python match case?

No. Only the first matching case runs and there is no fall-through.

What is the default case in Python match?

case _: matches any value, so it acts as the default.

Is match faster than if elif?

Not meaningfully. Choose match when it makes the code clearer, especially when unpacking structured data.

Python Operators & Booleans

Python Tutorial › Basics › Operators & Booleans

Python Operators & Booleans

BeginnerLesson 12 of 4712 min read12 runnable examples

Operators are the symbols that do the work in an expression: they add numbers, compare values and combine conditions. This lesson covers all seven groups of Python operators and the booleans they produce.

In this lesson
  • Arithmetic and assignment operators
  • Comparison operators and chaining
  • and, or, not with short-circuiting
  • is vs == and the in operator

Arithmetic operators

Python
a, b = 14, 4print(a + b, a - b, a * b)print(a / b, a // b, a % b, a ** 2)
Output
18 10 56
3.5 3 2 196

+ and * also work on strings and lists:

Python
print("ab" + "cd", "-" * 10)print([1, 2] + [3], [0] * 3)
Output
abcd ----------
[1, 2, 3] [0, 0, 0]

The Numbers & Math lesson explains //, % and rounding in detail.

Assignment operators

Augmented assignment updates a variable using its current value.

OperatorSame as
x += 3x = x + 3
x -= 3x = x - 3
x *= 3x = x * 3
x /= 3x = x / 3
x //= 3x = x // 3
x %= 3x = x % 3
x **= 3x = x ** 3
Python
balance = 1000balance -= 250      # withdrawalbalance *= 1.05     # 5% interestprint(balance)
Output
787.5

Comparison operators

Comparisons return a boolean: True or False.

OperatorMeaning
==equal
!=not equal
> / <greater than / less than
>= / <=greater or equal / less or equal
Python
score = 72print(score == 72, score != 72)print(score > 50, score <= 60)print("apple" < "banana")     # strings compare alphabeticallyprint(10 == 10.0)             # int and float with the same value are equal
Output
True False
True False
True
True

Python lets you chain comparisons, just like in maths:

Python
age = 25print(18 <= age < 60)
Output
True

Logical operators: and, or, not

ExpressionTrue when
a and bboth are true
a or bat least one is true
not aa is false
Python
has_ticket = Trueis_adult = Falseprint(has_ticket and is_adult)print(has_ticket or is_adult)print(not is_adult)
Output
False
True
True

Short-circuiting

and stops at the first falsy value and or stops at the first truthy value. They return that value itself, not always True/False. This gives two handy idioms:

Python
name = ""display = name or "Guest"          # default when name is emptyprint(display) items = []print(len(items) > 0 and items[0])  # items[0] is never evaluated
Output
Guest
False

Booleans and truthiness

True and False are the two boolean values, and bool is a subclass of int. In an if or while, any value is treated as true or false:

Python
print(True + True, True * 10)for value in [0, 3, "", "text", [], [1], None]:    print(repr(value), "->", "truthy" if value else "falsy")
Output
2 10
0 -> falsy
3 -> truthy
'' -> falsy
'text' -> truthy
[] -> falsy
[1] -> truthy
None -> falsy

Identity operators: is, is not

== asks “do these have the same value?” is asks “are these the very same object?”

Python
a = [1, 2]b = [1, 2]c = aprint(a == b, a is b)print(a is c)
Output
True False
True
Tip

Use is only to compare with None, True or False: if result is None:. For everything else use ==.

Membership operators: in, not in

Python
roles = ["admin", "tester", "viewer"]print("tester" in roles)print("guest" not in roles)print("py" in "python")                  # substring checkprint("id" in {"id": 7, "name": "x"})   # checks dict keys
Output
True
True
True
True

Bitwise operators

Bitwise operators work on the binary digits of integers. You will meet them in permissions, flags and low-level code.

Python
a, b = 6, 3       # 110 and 011 in binaryprint(a & b, a | b, a ^ b)print(~a, a << 1, a >> 1)
Output
2 7 5
-7 12 3

Operator precedence

From highest to lowest (operators on the same row are evaluated left to right, except **):

PrecedenceOperators
1() parentheses
2**
3+x, -x, ~x
4*, /, //, %
5+, -
6bitwise: << and >>, then &, then ^, then bitwise OR
7comparisons, in, not in, is, is not
8not
9and
10or
Python
print(True or False and False)    # and binds tighter: True or (False and False)print(not 1 == 2)                 # not (1 == 2)
Output
True
True

Check your understanding

Predict the output

x = 5print(x > 3 and x < 10, x > 3 or 1 / 0, 0 or "" or "last")

Quick quiz

1. What does 10 != 10.0 return?

2. What does "" or "default" return?

3. Which checks whether x is None?

4. What does 1 < 3 < 2 evaluate to?

Exercise: leap year check

A year is a leap year if it is divisible by 4, except years divisible by 100, unless they are also divisible by 400. Print each year with True or False.

Input
1900, 2000, 2024, 2026
Expected output
1900 False
2000 True
2024 True
2026 False
Hint 1

Combine % with and and or.

Hint 2

(year % 4 == 0 and year % 100 != 0) or year % 400 == 0

Program Practice

Python operators FAQ

What is the difference between == and is in Python?

== compares values; is compares identity (whether two names refer to the same object). Two separate lists with the same items are == but not is.

Does Python have && and || operators?

No. Python uses the words and, or and not.

Does Python have ++ and — operators?

No. Use x += 1 and x -= 1.

What does the % operator do in Python?

It returns the remainder of a division (17 % 5 is 2). It is also used in old-style string formatting such as "%d items" % 3.

Python Type Casting

Python Tutorial › Basics › Type Casting

Python Type Casting

BeginnerLesson 11 of 479 min read8 runnable examples

Type casting means converting a value from one type to another, such as turning the text “42” from input() into the number 42. Python does some conversions for you, and you do the rest with built-in functions named after the types.

In this lesson
  • Implicit vs explicit conversion
  • Convert between numbers and strings
  • Convert between collections
  • Handle values that cannot be converted

Implicit conversion

When you mix an int and a float in arithmetic, Python converts the int to a float for you, so no precision is lost:

Python
qty = 3          # intprice = 19.5     # floattotal = qty * priceprint(total, type(total).__name__)print(True + True)    # bool is a kind of int: True is 1
Output
58.5 float
2

Python will not guess between text and numbers. Mixing them is an error:

Python
age = 30print("Age: " + age)
Output
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print("Age: " + age)
          ~~~~~~~~^~~~~
TypeError: can only concatenate str (not "int") to str

Explicit conversion functions

FunctionConverts toExampleResult
int(x)whole numberint("42"), int(9.8)42, 9
float(x)decimalfloat("3.5"), float(2)3.5, 2.0
str(x)textstr(100)'100'
bool(x)True/Falsebool(0), bool("no")False, True
list(x)listlist("abc")['a', 'b', 'c']
tuple(x)tupletuple([1, 2])(1, 2)
set(x)setset([1, 1, 2]){1, 2}
dict(x)dictdict([("a", 1)]){'a': 1}

Numbers and strings

Python
age = 30print("Age: " + str(age))        # number to textprint(int("42") + 8)             # text to numberprint(float("3.75") * 2)print(int(9.99), int(-9.99))     # int() truncates towards zeroprint(int("  15\n"))             # surrounding whitespace is ignored
Output
Age: 30
50
7.5
9 -9
15

int() can also read numbers written in other bases:

Python
print(int("ff", 16), int("0b1010", 0), int("777", 8))
Output
255 10 511

Values that cannot be converted

Python
print(int("3.5"))
Output
Traceback (most recent call last):
  File "main.py", line 1, in <module>
    print(int("3.5"))
          ~~~^^^^^^^
ValueError: invalid literal for int() with base 10: '3.5'

int() will not read a decimal string. Convert to float first, or catch the error:

Python
print(int(float("3.5"))) for text in ["12", "4.0", "abc"]:    try:        print(text, "->", int(text))    except ValueError:        print(text, "-> not a whole number")
Output
3
12 -> 12
4.0 -> not a whole number
abc -> not a whole number

Converting to bool

bool() returns False for “empty” or zero values and True for everything else.

Python
for v in [0, 0.0, "", "0", "False", [], [0], None]:    print(f"{v!r:8} -> {bool(v)}")
Output
0        -> False
0.0      -> False
''       -> False
'0'      -> True
'False'  -> True
[]       -> False
[0]      -> True
None     -> False
Watch out

bool("False") is True, because it is a non-empty string. To read a yes/no answer, compare the text: answer.strip().lower() in ("yes", "y", "true").

Between collections

Python
letters = list("banana")print(letters)print(set(letters))                    # duplicates removedprint(tuple(letters[:3]))print("".join(letters))               # list of strings back to one string pairs = [("chrome", 120), ("firefox", 118)]print(dict(pairs))print(list({"x": 1, "y": 2}))          # a dict converts to its keys
Output
['b', 'a', 'n', 'a', 'n', 'a']
{'a', 'b', 'n'}
('b', 'a', 'n')
banana
{'chrome': 120, 'firefox': 118}
['x', 'y']

Check your understanding

Predict the output

print(int("7") + float("2.5"), str(7) + str(2.5), int(True))

Quick quiz

1. What does int("3.9") do?

2. What does bool("0") return?

3. Which converts [3, 1, 3] to a collection with only unique items?

Exercise: sum numbers from text

A line of comma-separated numbers arrives as text. Convert it and print the total and the average.

Input
"12, 7.5, 30, 0.5"
Expected output
[12.0, 7.5, 30.0, 0.5]
Total: 50.0
Average: 12.5
Hint 1

split(",") gives the parts as strings; float() converts each one.

Program Practice

Python type casting FAQ

How do I convert a string to an integer in Python?

Use int("42"). If the string may contain a decimal point, use int(float(text)), and wrap it in try / except ValueError for user input.

How do I convert an integer to a string?

Use str(42), or put it in an f-string: f"{42}".

What is the difference between implicit and explicit type conversion?

Implicit conversion is done by Python automatically, such as int to float in 3 * 1.5. Explicit conversion is when you call a function such as int() or str() yourself.

How do I convert a list to a string?

Use ", ".join(items) for a list of strings. For other values, convert each one first: ", ".join(map(str, numbers)).

Python Numbers & Math

Python Tutorial › Basics › Numbers & Math

Python Numbers & Math

BeginnerLesson 10 of 4711 min read12 runnable examples

Python has three number types and a rich set of tools for working with them. This lesson covers arithmetic, rounding, the float precision trap, the math module and random numbers.

In this lesson
  • int, float and complex numbers
  • Arithmetic including // and %
  • Rounding and float precision
  • The math and random modules

Three number types

Python
count = 42          # int: whole number, any sizeratio = 0.75        # float: decimal numbersignal = 3 + 4j     # complex: real + imaginary part print(type(count).__name__, type(ratio).__name__, type(signal).__name__)print(abs(signal))  # length of the complex number
Output
int float complex
5.0

Large numbers are easier to read with underscores. Python ignores them:

Python
population = 1_428_627_663print(population)print(2 ** 64)
Output
1428627663
18446744073709551616

Arithmetic operators

OperatorMeaning17 ? 5
+add22
-subtract12
*multiply85
/divide (always float)3.4
//floor divide (round down)3
%remainder (modulo)2
**power1419857
Python
a, b = 17, 5print(a + b, a - b, a * b)print(a / b, a // b, a % b)print(a ** b)
Output
22 12 85
3.4 3 2
1419857

// and % work as a pair: a == (a // b) * b + a % b. divmod() gives both at once. A classic use is converting seconds:

Python
seconds = 3725minutes, secs = divmod(seconds, 60)hours, minutes = divmod(minutes, 60)print(f"{hours}h {minutes}m {secs}s")
Output
1h 2m 5s
Tip

n % 2 == 0 is the standard test for an even number. Many practice programs use it.

Order of operations

Python follows the usual maths rules: * first, then , /, //, %, then + and -. Use parentheses to make your intent clear.

Python
print(2 + 3 * 4)print((2 + 3) * 4)print(2 ** 3 ** 2)    # ** groups from the right: 2 ** 9print(-2 ** 2)        # ** binds tighter than the minus sign
Output
14
20
512
-4

Rounding

Python
price = 2.675print(round(price, 2))print(round(2.5), round(3.5))     # ties round to the even numberprint(int(7.9), int(-7.9))        # int() cuts off the decimals
Output
2.67
2 4
7 -7

round(2.5) gives 2 because Python uses “banker’s rounding”: a tie goes to the nearest even number. round(2.675, 2) gives 2.67 because 2.675 cannot be stored exactly as a float; it is really slightly less.

Float precision

Python
print(0.1 + 0.2)print(0.1 + 0.2 == 0.3) import mathprint(math.isclose(0.1 + 0.2, 0.3))
Output
0.30000000000000004
False
True

For money, use Decimal with string inputs, which stores decimal digits exactly:

Python
from decimal import Decimal print(Decimal("0.1") + Decimal("0.2"))total = Decimal("19.99") * 3print(total)
Output
0.3
59.97
Watch out

Never compare floats with ==. Use math.isclose() or compare rounded values.

Useful built-in functions

Python
nums = [4, -9, 15, 2]print(abs(-9), max(nums), min(nums), sum(nums))print(pow(2, 10), pow(2, 10, 1000))   # third argument: remainder
Output
9 15 -9 12
1024 24

The math module

Python
import math print(math.sqrt(144))print(math.floor(4.7), math.ceil(4.2))print(math.pi)print(math.factorial(5))print(math.gcd(48, 36), math.lcm(4, 6))print(round(math.log10(1000)), round(math.hypot(3, 4)))
Output
12.0
4 5
3.141592653589793
120
12 12
3 5

Random numbers

The random module picks pseudo-random values. random.seed() makes the sequence repeatable, which is useful in tests.

Python
import random random.seed(7)print(random.randint(1, 6))           # dice roll: 1 to 6 inclusiveprint(random.choice(["red", "green", "blue"]))print(round(random.random(), 3))      # float from 0.0 up to 1.0
Output
3
red
0.395

Number systems

Python
print(bin(10), oct(10), hex(255))print(int("1010", 2), int("ff", 16))
Output
0b1010 0o12 0xff
10 255

Check your understanding

Predict the output

print(7 // 2, -7 // 2, 7 % 3, -7 % 3)

Quick quiz

1. What does 10 / 2 return?

2. What does round(2.5) return?

3. How do you get the remainder of 17 divided by 5?

4. Which is the safe way to compare two floats?

Exercise: simple interest

Calculate simple interest and the total amount, rounded to 2 decimals.

Input
principal = 15000, rate = 7.5 (% per year), years = 3
Expected output
Interest: 3375.0
Total: 18375.0
Hint 1

Simple interest is principal * rate * years / 100.

Program Practice

Python numbers and math FAQ

What is the difference between / and // in Python?

/ is true division and always returns a float (7 / 2 is 3.5). // is floor division and rounds down (7 // 2 is 3).

Why does 0.1 + 0.2 not equal 0.3 in Python?

Floats are stored in binary, which cannot represent 0.1 exactly. The result is 0.30000000000000004. Use math.isclose() or decimal.Decimal when exactness matters.

How do I round a number to 2 decimal places in Python?

Use round(x, 2) to get a number, or f"{x:.2f}" to get a string for display.

How do I get the square root in Python?

Use math.sqrt(x) after import math, or x ** 0.5.

Python User Input

Python Tutorial › Getting Started › User Input

Python User Input

BeginnerLesson 7 of 478 min read7 runnable examples

input() pauses your program and waits for the user to type something and press Enter. It is the simplest way to make a program interactive, and it is used in many practice programs.

In this lesson
  • Read text with input()
  • Convert input to numbers
  • Read several values from one line
  • Handle input that is not a number

Read text with input()

input() shows an optional prompt, waits for the user and returns what they typed as a string.

Python
name = input("What is your name? ")print("Hello,", name)
Outputtyped input: Asha
What is your name? Asha
Hello, Asha
Note

When you press Try it and Run on this site, your browser shows a small box for each input(). The recorded output above shows a run where the user typed Asha, exactly as it appears in a terminal.

input() always returns a string

Even if the user types digits, you get a string. Adding two strings joins them instead of adding numbers:

Python
a = input("First number: ")b = input("Second number: ")print(a + b)print(type(a))
Outputtyped input: 5, 7
First number: 5
Second number: 7
57
<class 'str'>

Convert input to a number

Wrap input() in int() for whole numbers or float() for decimals.

Python
qty = int(input("Quantity: "))price = float(input("Price: "))print("Total:", qty * price)
Outputtyped input: 5, 7.5
Quantity: 5
Price: 7.5
Total: 37.5
You wantUseExample inputResult
Textinput()pune'pune'
Whole numberint(input())4242
Decimal numberfloat(input())3.753.75

Several values on one line

split() breaks a string on spaces and returns a list. Combine it with map() to convert every part:

Python
numbers = input("Enter numbers separated by spaces: ").split()print(numbers) values = list(map(int, numbers))print(sum(values))
Outputtyped input: 10 20 30
Enter numbers separated by spaces: 10 20 30
['10', '20', '30']
60

Unpack directly into variables when you know how many values to expect:

Python
width, height = map(int, input("Width and height: ").split())print("Pixels:", width * height)
Outputtyped input: 1920 1080
Width and height: 1920 1080
Pixels: 2073600

Handle invalid input

If the user types abc, int() cannot convert it and raises a ValueError:

Python
age = int(input("Age: "))
Outputtyped input: abc
Age: abc
Traceback (most recent call last):
  File "main.py", line 1, in <module>
    age = int(input("Age: "))
ValueError: invalid literal for int() with base 10: 'abc'

Catch the error with try / except and ask again. The Exception Handling lesson explains this pattern fully.

Python
while True:    text = input("Age: ")    try:        age = int(text)    except ValueError:        print(f"{text!r} is not a whole number, try again")        continue    if age < 0:        print("Age cannot be negative")        continue    break print("Age saved:", age)
Outputtyped input: abc, -3, 29
Age: abc
'abc' is not a whole number, try again
Age: -3
Age cannot be negative
Age: 29
Age saved: 29
Tip

text.strip() removes spaces the user typed by accident before or after the value. Use it before comparing input, for example input().strip().lower() == "yes".

Check your understanding

Quick quiz

1. The user types 8. What is the type of input()‘s result?

2. What does "3" + "4" give?

3. Which reads two whole numbers typed on one line as 4 9?

Exercise: rectangle calculator

Ask for the width and height on one line and print the area and the perimeter.

Input
4 6
Expected output
Width and height: 4 6
Area: 24
Perimeter: 20
Hint 1

split() then map(int, ...) gives you two numbers.

Hint 2

Perimeter is 2 * (width + height).

Program Practice

Many basic programs read their values with input(). Try changing a solution to ask the user instead of using fixed values.

Python input FAQ

How do I take integer input in Python?

Wrap input() in int(): n = int(input("Enter a number: ")). Use float() for decimal numbers.

How do I take multiple inputs in one line?

Use split(): a, b = input().split(). Add map(int, ...) to convert them to numbers.

What happens if the user enters text when I expect a number?

int() raises ValueError. Catch it with try / except ValueError and ask again.

Is raw_input() still available?

No. raw_input() was Python 2. In Python 3, input() does the same job and always returns a string.