What’s new in Python 3.12-3.14
The version-currency file. Python 3.14 shipped October 2025; 3.14.x is current as of 2026-08. Knowing what landed recently is a cheap way to sound current, and misdescribing it is a cheap way not to.
Python 3.14 — the headline changes
| Feature | PEP | What it means |
|---|---|---|
| Free-threaded build officially supported | 779 | no longer experimental; still an optional build |
| Multiple interpreters in the stdlib | 734 | concurrent.interpreters; a fourth concurrency model |
| Template strings (t-strings) | 750 | safe interpolation primitives |
| Deferred annotation evaluation | 649 | annotations are lazy by default |
compression.zstd |
784 | Zstandard in the stdlib |
| Zero-overhead external debugger | — | attach to a running process safely |
The first two are covered in ../04_async_concurrency/01_gil.md. The next two matter for everyday code.
Template strings (PEP 750)
An f-string evaluates to a str immediately. A t-string evaluates to a Template object holding the static parts and the interpolated values separately — so a library can process them before they’re combined.
from string.templatelib import Template
name = "Robert'); DROP TABLE students;--"
t = t"SELECT * FROM users WHERE name = {name}"
type(t) # string.templatelib.Template, NOT str
The point: the boundary between literal text and interpolated data survives, so a library can escape, parameterise or validate correctly. That’s exactly the boundary f-strings destroy, which is why f-strings in SQL and HTML are injection bugs.
# f-string: the boundary is gone before your library sees it
query = f"SELECT * FROM users WHERE name = '{name}'" # SQL injection
# t-string: the library receives the parts and can parameterise
query = sql(t"SELECT * FROM users WHERE name = {name}")
t-strings are a primitive for library authors, not a drop-in fix. The value arrives as libraries (database drivers, HTML templating, logging) add Template support. The interview-relevant point is understanding why the separation matters.
Deferred annotations (PEP 649)
Annotations are now evaluated lazily, on demand, rather than at definition time.
class Node:
parent: Node | None # works without quotes or __future__ import
children: list[Node]
What changes:
- Forward references just work. No more string quoting or
from __future__ import annotations. - Import cost drops — annotations aren’t evaluated unless something asks for them.
typing.get_type_hints()still resolves them to real objects, unlike the__future__behaviour which left them as strings.
The __future__ import annotations approach is now largely unnecessary. If you see it in new code, it’s a habit rather than a requirement.
Python 3.13 recap
| Feature | Note |
|---|---|
| Experimental free-threaded build | promoted to official in 3.14 |
| Experimental JIT (copy-and-patch) | still experimental |
| Improved REPL | multiline editing, colour |
| Better error messages | continues a long-running trend |
dbm.sqlite3 |
new default dbm backend |
Python 3.12 recap
Still widely deployed, so worth knowing:
| Feature | PEP | Note |
|---|---|---|
| Type parameter syntax | 695 | def f[T](x: T) -> T: — no explicit TypeVar |
| f-string grammar formalised | 701 | nested quotes, multiline, comments allowed |
type statement |
695 | type Alias = int | str |
| Per-interpreter GIL | 684 | the groundwork for PEP 734 |
itertools.batched |
— | chunking without a recipe |
@override decorator |
698 | catches renamed base methods |
PEP 695 generics are the one to use in new code:
# 3.12+
def first[T](items: list[T]) -> T | None:
return items[0] if items else None
type Json = dict[str, "Json"] | list["Json"] | str | int | float | bool | None
See typing/02_typevar_generics.md.
The support window
| Version | Status (2026-08) |
|---|---|
| 3.9 and earlier | EOL |
| 3.10, 3.11 | security fixes only |
| 3.12, 3.13 | supported |
| 3.14 | current |
Anything describing 3.7-3.9 idioms as “modern” is several years stale. New projects should target 3.13 or 3.14.
Interview angle
- “What’s new in recent Python?” — 3.14: free-threading officially supported (PEP 779), stdlib subinterpreters (PEP 734), t-strings (PEP 750), deferred annotations (PEP 649). 3.12: PEP 695 type parameter syntax. Naming the PEP numbers is optional; knowing what each does is not.
- “What problem do t-strings solve?” — they preserve the boundary between literal text and interpolated values, so a library can escape or parameterise correctly. F-strings destroy that boundary before the library sees it, which is why f-string SQL is an injection bug. t-strings are a primitive for library authors, not an automatic fix.
- “What changed with annotations in 3.14?” — PEP 649 made evaluation lazy, so forward references work without quotes or the
__future__import, imports get cheaper, andget_type_hints()still resolves to real objects rather than strings. - “How do you write a generic function in modern Python?” — PEP 695 syntax:
def first[T](items: list[T]) -> T | None. No explicitTypeVardeclaration, andtype X = ...for aliases. - “Which Python version would you target for a new service?” — 3.13 or 3.14. 3.9 and earlier are EOL, 3.10 and 3.11 are security-only. If you want free-threading, 3.14 with the caveat that it’s an optional build and your C extensions need to support it.