Python 3.14 new features headline a release that finally makes two long-promised ideas official: string templating without eval-time surprises, and a Python interpreter that can run threads without the Global Interpreter Lock getting in the way. Python 3.14 was released on October 7, 2025, and the official What's New in Python 3.14 page along with the full Python 3.14 changelog lay out everything that shipped. If you have been putting off reading the Python 3.14 release notes, this is the practical rundown: what changed, why it matters, and what will break when you upgrade.

This is not a minor point release. Between t-strings, deferred annotation evaluation, and free-threaded builds becoming a supported (not experimental) configuration, Python 3.14 changes how a lot of "advanced" Python code gets written. Below is a complete tour of the top Python 3.14 new features, followed by migration notes on the Python 3.14 breaking changes that matter, gotchas, and a verdict on whether you should upgrade now.

Template Strings (t-strings) Replace Ad-Hoc String Building

Template strings are the single most talked-about of all the Python 3.14 new features, delivered through PEP 750. If you already use f-strings, t-strings will look familiar — same interpolation syntax, different prefix, and a very different return type.

An f-string evaluates immediately to a str. A t-string evaluates to a Template object from the new string.templatelib module, which keeps the static text and the interpolated expressions separate instead of smashing them together into a finished string. That separation is what makes t-strings useful for things f-strings were never safe for: building SQL fragments, generating HTML, or writing a logging wrapper that can inspect the raw values before they are formatted.

Here is the difference in practice:

python
from string.templatelib import Template, Interpolation

name = "O'Brien"
age = 41

# The old way: an f-string commits to a final string immediately
greeting = f"Hello {name}, you are {age} years old."
print(greeting)

# The new way: a t-string keeps structure so you can process it safely
template: Template = t"Hello {name}, you are {age} years old."

parts = []
for item in template:
    if isinstance(item, Interpolation):
        # item.value is the raw Python object, not a stringified one
        parts.append(str(item.value).replace("'", "'"))
    else:
        parts.append(item)

safe_greeting = "".join(parts)
print(safe_greeting)

Because the template keeps a handle on the original value of name before it becomes text, a function consuming the template can decide how to escape or transform it. That is exactly what a web framework needs to stop HTML injection, and what a database layer needs to bind parameters instead of concatenating raw strings. Expect libraries for safe HTML rendering and SQL building to adopt t-strings quickly.

Deferred Evaluation of Annotations Changes How Type Hints Run

PEP 649 is the quieter but arguably more consequential of the Python 3.14 new features. Type annotations on functions, classes, and modules are no longer evaluated at definition time by default — they are evaluated lazily, on first access, via a mechanism the PEP calls deferred evaluation.

Previously, if you wanted to avoid the cost (and the circular-import headaches) of evaluating annotations eagerly, you reached for from future import annotations, which turned every annotation into a plain string. That trick worked, but it meant tools calling typing.get_type_hints() had to re-parse strings, and forward references needed manual quoting. Python 3.14 makes the lazy behavior the default without turning annotations into strings — they stay as real objects, just evaluated only when something asks for them.

python
from __future__ import annotations  # still works, but no longer required for this trick

class Inventory:
    # 'Item' does not need to exist yet when this class body runs in 3.14
    items: list["Item"]

    def add(self, item: "Item") -> None:
        self.items.append(item)

class Item:
    def __init__(self, name: str, quantity: int) -> None:
        self.name = name
        self.quantity = quantity

inventory = Inventory()
inventory.items = []
inventory.add(Item("USB cable", 3))
print([f"{i.name}: {i.quantity}" for i in inventory.items])

# Annotations are only resolved when you actually ask for them
import inspect
print(inspect.get_annotations(Inventory.add))

The practical win: forward references inside a module resolve naturally, import time drops because annotation-heavy code (think large dataclass or Pydantic-style models) no longer pays evaluation cost for annotations nobody inspects, and circular type references between two classes in the same file stop needing string quoting.

Free-Threaded Python: The Most Ambitious of the Python 3.14 New Features

Python 3.13 shipped an experimental build of free-threaded Python without the Global Interpreter Lock. Python 3.14 promotes that work from experimental to officially supported through PEP 779. This does not mean the GIL is gone from the default python.org build — it means the free-threaded Python build (python3.14t) is no longer a "use at your own risk" side project; the core team commits to keeping it working and reasonably performant.

The headline number worth remembering: single-threaded code on the free-threaded Python build now runs with roughly a 5-10% overhead compared to the standard build, a big improvement from earlier free-threaded prototypes, largely because the specializing adaptive interpreter now runs in free-threaded mode too.

python
import threading
import time

def cpu_bound_work(n: int) -> int:
    total = 0
    for i in range(n):
        total += i * i
    return total

def run_threaded(thread_count: int, work_size: int) -> float:
    threads = []
    start = time.perf_counter()
    for _ in range(thread_count):
        t = threading.Thread(target=cpu_bound_work, args=(work_size,))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    return time.perf_counter() - start

if __name__ == "__main__":
    elapsed = run_threaded(thread_count=4, work_size=5_000_000)
    print(f"Ran 4 CPU-bound threads in {elapsed:.2f} seconds")
    print("On a GIL build this barely parallelizes.")
    print("On python3.14t (free-threaded), it can use multiple cores.")

Run this script on a standard build and the four threads mostly take turns because of the GIL. Run it on the free-threaded build and you should see real wall-clock improvement on a multi-core machine, since CPU-bound Python threads can now execute concurrently instead of just interleaving during I/O waits.

Multiple Interpreters: Another of the Python 3.14 New Features for Concurrency

PEP 734 adds a new concurrent.interpreters module, exposing the subinterpreter machinery that has existed in CPython's C API for years but was never reachable from pure Python. Each interpreter gets its own GIL (or none, on a free-threaded build) and its own module state, so it sidesteps GIL contention differently than threads and avoids the process-startup cost of multiprocessing. This is a newer, less battle-tested corner of this Python 3.14 release, worth watching as it matures.

python
from concurrent.interpreters import create, create_queue

def worker(queue) -> None:
    while True:
        message = queue.get()
        if message is None:
            break
        print(f"Interpreter received: {message}")

results = create_queue()
interp = create()
interp.call(worker, results)

for value in ["first job", "second job", None]:
    results.put(value)

interp.close()
print("Subinterpreter finished processing the queue.")

Data cannot flow freely between interpreters the way it can between threads sharing memory — only a limited set of shareable types (None, bool, int, float, str, bytes, and a few others) can cross the boundary, typically through a queue created with create_queue(). That constraint is deliberate: it is what lets each interpreter run in true isolation without needing its own lock dance. For CPU-bound work that does not fit neatly into free-threading yet, concurrent.futures.InterpreterPoolExecutor (built on top of this module) is a drop-in-feeling alternative to ProcessPoolExecutor with lower overhead.

compression.zstd: A Practical Pick Among the Python 3.14 New Features

Projects have depended on third-party packages like zstandard or pyzstd for years just to get fast, high-ratio compression. PEP 784 folds Meta's Zstandard algorithm directly into the standard library as compression.zstd, and reorganizes the older bz2, lzma, gzip, and zlib modules under a shared compression namespace (your existing imports keep working).

python
from compression.zstd import ZstdFile, open as zstd_open

sample_text = ("Python 3.14 makes Zstandard compression available "
               "without any third-party dependency.\n") * 2000
raw_bytes = sample_text.encode("utf-8")

with ZstdFile("sample.zst", mode="wb", level=12) as f:
    f.write(raw_bytes)

with zstd_open("sample.zst", mode="rb") as f:
    restored = f.read()

assert restored == raw_bytes
print(f"Original size: {len(raw_bytes)} bytes")

import os
print(f"Compressed size: {os.path.getsize('sample.zst')} bytes")
os.remove("sample.zst")

Zstandard sits in a genuinely useful spot: compression ratios close to lzma with speed much closer to zlib. For anything shipping compressed payloads over a network or writing large log or cache files to disk, this removes a dependency that almost every data-heavy Python project used to need — and it may be the most immediately useful of all the Python 3.14 new features for backend teams.

Cleaner Exception Handling With except Without Parentheses

A small syntax change with an outsized quality-of-life effect: PEP 758 allows except and except* clauses to list multiple exception types without wrapping them in parentheses, as long as you are not also binding the exception to a name with as. It is one of the smallest Python 3.14 new features on this list, but it is the one you will type the most often.

python
def parse_config_value(raw: str) -> int | float:
    try:
        if "." in raw:
            return float(raw)
        return int(raw)
    except ValueError, TypeError:
        print(f"Could not parse {raw!r} as a number, defaulting to 0")
        return 0

print(parse_config_value("42"))
print(parse_config_value("3.14"))
print(parse_config_value("not-a-number"))

Compare that except ValueError, TypeError: to the Python 3.13 requirement of except (ValueError, TypeError):. Both still work in Python 3.14 — the parenthesized form is not deprecated, it is just no longer mandatory. The moment you add as err to capture the exception instance, the parentheses come back: except (ValueError, TypeError) as err:. If you want the exact grammar diff, it is documented in the full Python 3.14 changelog.

A Genuinely Useful PyREPL: Syntax Highlighting Out of the Box

Python 3.13 replaced the decades-old REPL with a PyPy-derived PyREPL that added multiline editing and history search. Python 3.14 builds on that with real-time syntax highlighting as you type — keywords, strings, numbers, and comments each get distinct colors — plus tab-completion for module names inside import statements, and colorized tracebacks driven by a new internal _colorize module that you can theme yourself by calling into _colorize.set_theme().

If you are running Python 3.14 in a terminal that supports ANSI colors, just opening python at a shell prompt and typing a few lines shows the difference immediately: no plugins, no IPython required. Colors respect the PYTHON_COLORS and NO_COLOR environment variables, so CI logs and piped output are not filled with escape codes by default. The REPL work rounds out the interactive side of the Python 3.14 new features in this release.

Python 3.14 Breaking Changes: A Migration Notes Checklist to Migrate to Python 3.14 Safely

Most Python 3.14 code will run unmodified, but a handful of removals and behavior changes are worth auditing before you flip your production interpreter:

  • ast.Num, ast.Str, ast.Bytes, ast.NameConstant, and ast.Ellipsis, deprecated since Python 3.8, are removed. Anything walking an AST needs to use ast.Constant instead.
  • The deprecated asyncio child watcher classes — MultiLoopChildWatcher, FastChildWatcher, AbstractChildWatcher, and SafeChildWatcher — are gone. Code managing subprocesses through asyncio should rely on the default child watcher behavior instead of instantiating these directly.
  • pkgutil.find_loader() and pkgutil.get_loader() are removed in favor of importlib.util.find_spec().
  • itertools no longer supports copy, deepcopy, or pickle on iterator objects — that support was undocumented, inconsistent, and buggy, so code depending on it needs to materialize the iterator into a list first.
  • The onerror argument to shutil.rmtree() is removed; use onexc instead, which receives the exception object rather than exc_info.
  • On Linux, BSDs, and other non-macOS POSIX platforms, multiprocessing's default start method is changing away from fork toward a safer option, which affects code that relies on child processes implicitly inheriting parent state — pass an explicit start method if your code depends on fork semantics.
  • If you use from future import annotations today, nothing forces you to remove it, but you can start dropping the quotes around forward references in new code once you are targeting 3.14 only, since deferred evaluation handles that natively.

The official porting guide in the What's New in Python 3.14 document is worth a full read-through if your codebase touches ast, asyncio subprocess management, or multiprocessing directly. Cross-reference it against the full Python 3.14 changelog before you migrate to Python 3.14 across an entire fleet of services.

Gotchas to Watch For After Upgrading to This Python 3.14 Release

A few rough edges show up in practice once teams start running these Python 3.14 new features for real:

  • Deferred annotation evaluation means a typo in an annotation that used to fail loudly at import time can now fail later, the first time something calls typing.get_type_hints() or inspect.get_annotations() on it. Run your test suite with something that actually exercises type hints (a validator, a serializer, mypy in strict CI) rather than assuming import success means your annotations are valid.
  • t-strings are easy to reach for reflexively, but they are not a drop-in replacement for f-strings in hot paths — building the Template object and walking it is more work than an f-string's direct formatting, so keep using f-strings for plain string building and reserve t-strings for cases where you actually need to inspect or transform the pieces before turning them into text.
  • The free-threaded Python build (python3.14t) is a separate installable variant, not the default python3.14 binary. If you install Python 3.14 through your usual package manager and run threaded benchmarks expecting GIL-free parallelism, you will be disappointed until you explicitly install the free-threaded Python build.
  • C extensions that are not yet updated for free-threading can silently re-enable the GIL at import time on a free-threaded build, so "it imported fine" is not proof that you are getting real thread parallelism — check sys._is_gil_enabled() if you need to be sure.
  • except ValueError, TypeError: without parentheses is easy to misread at a glance as a tuple literal missing its parens, especially for anyone coming from a codebase that has used the parenthesized style for years. Consider a linter rule or team convention rather than relying on habit.

Verdict: Should You Migrate to Python 3.14 Now?

For most application and library code, yes — upgrade now, but treat the migration notes above as a checklist rather than an afterthought. Template strings and deferred annotations are additive: your existing f-strings and eagerly-evaluated annotations keep working exactly as before, so none of the Python 3.14 new features force a rewrite. The removed deprecated APIs (ast.Num and friends, the asyncio child watchers, pkgutil loaders) are the most visible Python 3.14 breaking changes, and a quick grep across your codebase will tell you in minutes whether you are affected. The bottom line: most teams should migrate to Python 3.14 sooner rather than later, and this Python 3.14 release earns that recommendation.

The one place to slow down before you migrate to Python 3.14 fleet-wide is free-threading. If your workload is CPU-bound and multi-threaded, and your dependencies already publish free-threaded Python wheels, Python 3.14's officially supported no-GIL build is worth a serious evaluation — potentially a big win. If you depend on C extensions that have not caught up yet, stick with the standard build for now and revisit in a release or two; PEP 779 explicitly plans for GIL-optional support to mature over several more releases before the GIL is ever disabled by default. Everyone else gets a genuinely pleasant upgrade: better REPL ergonomics, a useful new compression module, and syntax cleanups that make everyday code a little easier to read.

Putting It Together: Python 3.14 New Features in One Example

This closing example combines a few of the release's headline features — template strings for safe interpolation, deferred annotations for forward references, and the parenthesis-free except syntax — into one self-contained script:

python
from string.templatelib import Template, Interpolation

class Order:
    # Forward reference to LineItem resolves lazily thanks to PEP 649
    items: list["LineItem"]

    def __init__(self) -> None:
        self.items = []

    def add(self, item: "LineItem") -> None:
        self.items.append(item)

    def total(self) -> float:
        return sum(item.price * item.quantity for item in self.items)

class LineItem:
    def __init__(self, name: str, price: float, quantity: int) -> None:
        self.name = name
        self.price = price
        self.quantity = quantity

def render_receipt(order: Order) -> str:
    parts = []
    header: Template = t"Receipt for {len(order.items)} item(s):\n"
    for item in header:
        parts.append(str(item.value) if isinstance(item, Interpolation) else item)

    for line in order.items:
        row: Template = t"  {line.name} x{line.quantity} = ${line.price * line.quantity:.2f}\n"
        for item in row:
            if isinstance(item, Interpolation):
                parts.append(f"{item.value:{item.format_spec}}" if item.format_spec else str(item.value))
            else:
                parts.append(item)

    return "".join(parts)

def safe_total(order: Order) -> float:
    try:
        return order.total()
    except ValueError, TypeError:
        print("Could not compute total due to bad line item data")
        return 0.0

order = Order()
order.add(LineItem("Mechanical keyboard", 89.99, 1))
order.add(LineItem("USB-C cable", 8.50, 3))

print(render_receipt(order))
print(f"Total: ${safe_total(order):.2f}")

That is a snapshot of the Python 3.14 new features that matter most in one script: familiar syntax, a few sharp new tools, and nothing that forces you to rewrite what already works.