What's New in Python

A new version of Python is released every October, each supported for five years. If you're reading older tutorials or maintaining older code, it helps to know which features arrived when. This lesson summarises the most useful additions from Python 3.8 to 3.14.

Release timeline

VersionReleasedHeadline features
3.82019Walrus operator :=, f-string = debugging, positional-only parameters
3.92020Dict merge |, list[int] hints, str.removeprefix, zoneinfo
3.102021match statements, X | Y type unions, much better error messages
3.11202210–60% faster, exception groups and except*, tomllib, error locations in tracebacks
3.122023Flexible f-strings, type statement and generic syntax, itertools.batched
3.132024New interactive shell with colours and multi-line editing, experimental free-threaded build and JIT
3.142025Template strings (t-strings), deferred annotations, officially supported free-threaded build

Each version is maintained for about five years. Check python3 --version, and prefer a version that still receives updates.

Better error messages (3.10–3.13)

Recent versions point at the exact part of a line that failed and suggest fixes:

PY errors.py
user = {"name": "Ana"}
print(usr["name"])
Output
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print(usr["name"])
          ^^^
NameError: name 'usr' is not defined. Did you mean: 'user'?

Pattern matching (3.10)

PY match.py
def handle(command):
    match command.split():
        case ["load", filename]:
            return f"loading {filename}"
        case ["save", *_]:
            return "saving"
        case _:
            return "unknown"

print(handle("load report.csv"), "|", handle("save as"), "|", handle("quit"))
Output
loading report.csv | saving | unknown

Covered in full in lesson 15.

Modern type hint syntax (3.9–3.12)

PY typing.py
type Grid = list[list[int]]

def first[T](items: list[T]) -> T | None:
    return items[0] if items else None

def total(rows: Grid) -> int:
    return sum(sum(r) for r in rows)

print(first(["a", "b"]), total([[1, 2], [3, 4]]))
Output
a 10

No more importing List, Optional and TypeVar from typing for everyday hints.

f-string improvements (3.8, 3.12)

PY fstrings.py
data = {"name": "Ana", "scores": [90, 85]}
total = sum(data["scores"])

print(f"{total=}")
print(f"{data["name"]} averaged {total / len(data["scores"]):.1f}")
print(f"{'\n'.join(['multi', 'line'])}")
Output
total=175
Ana averaged 87.5
multi
line

Since 3.12, f-strings can reuse the same quotes inside braces, contain backslashes, and span lines.

Exception groups and notes (3.11)

PY exception_groups.py
try:
    raise ExceptionGroup("validation failed", [ValueError("bad email"), TypeError("age not a number")])
except* ValueError as eg:
    print("value errors:", [str(e) for e in eg.exceptions])
except* TypeError as eg:
    print("type errors:", [str(e) for e in eg.exceptions])
Output
value errors: ['bad email']
type errors: ['age not a number']

Reading TOML config (3.11)

PY toml.py
import tomllib

config = tomllib.loads("""
[server]
host = "localhost"
port = 8080

[features]
beta = true
""")

print(config["server"]["port"], config["features"]["beta"])
Output
8080 True

TOML is the format of pyproject.toml; tomllib reads it without third-party packages.

Template strings (3.14)

A t prefix creates a template instead of a finished string. Code receiving it can see the literal parts and the values separately — for example, to escape values safely before building HTML or SQL:

PY tstrings.py
from html import escape
from string.templatelib import Interpolation

def safe_html(template):
    parts = []
    for item in template:
        if isinstance(item, Interpolation):
            parts.append(escape(str(item.value)))
        else:
            parts.append(item)
    return "".join(parts)

user_input = "<script>alert('hi')</script>"
print(safe_html(t"<p>Hello, {user_input}!</p>"))
Output
<p>Hello, &lt;script&gt;alert(&#x27;hi&#x27;)&lt;/script&gt;!</p>

An f-string would have inserted the script tag directly; the t-string lets the function escape each value before combining.

Performance and free threading

Python 3.11 brought large speed-ups (often 25% or more) through the "Faster CPython" project, and work continues each release, including an experimental just-in-time (JIT) compiler. Python 3.13 introduced an optional free-threaded build that removes the Global Interpreter Lock (GIL), allowing threads to run Python code truly in parallel on multiple CPU cores; 3.14 made it officially supported. The standard build still has the GIL, and libraries are gradually adding support.

Upgrading

  • Read the "What's New" page in the official documentation for each version you skip.
  • Run your tests on the new version before switching (tools like tox or nox test several versions at once).
  • Linters such as Ruff (with the pyupgrade rules) can automatically rewrite old syntax to modern equivalents.

Try it yourself

Try it yourself
Console output