What Is New in Python 3.14
Python 3.14 promotes free-threaded mode to a supported (non-experimental) feature, ships an improved JIT based on a tail-call interpreter, adds concurrent.interpreters for multi-interpreter concurrency, brings Zstandard compression into the stdlib, and introduces T-strings (template strings) as a new literal type for safe interpolation. The REPL gains syntax highlighting. Release signatures migrated from PGP to Sigstore.
| Category | Change | PEP / Reference |
|---|---|---|
| Concurrency | Free-threaded mode graduates from experimental to supported | PEP 703 |
| Performance | Tail-call interpreter -- 3-5% average speedup on pyperformance | -- |
| Concurrency | concurrent.interpreters module and InterpreterPoolExecutor |
PEP 734 |
| New Syntax | T-strings: t"Hello {name}" -- template string literals |
PEP 750 |
| New Modules | compression.zstd -- Zstandard compression in stdlib |
PEP 784 |
| REPL | Syntax highlighting while typing; import tab-completion | -- |
| Tooling | Release signatures now use Sigstore instead of PGP | -- |
| Standard Library | UUID v6/v7/v8 support; pathlib improvements; asyncio introspection |
-- |
| Breaking | multiprocessing default start method changes to forkserver on Linux/BSD |
-- |
What Are T-Strings and How Do They Differ from F-Strings?
T-strings (t"...", PEP 750) are template literals that do not immediately interpolate -- instead, they return a Template object with the static string parts and evaluated expressions accessible separately. This allows post-processing before rendering: escaping, formatting, localization, or query parameterization.
from string.templatelib import Template
name = "Alice & Bob"
greeting = t"Hello, {name}!"
# greeting is a Template object, not a string
for part in greeting:
print(type(part), repr(part))
# StringPart 'Hello, '
# Interpolation <name=Alice & Bob>
# StringPart '!'
# Safe HTML rendering:
def to_html(tmpl: Template) -> str:
parts = []
for part in tmpl:
if isinstance(part, str):
parts.append(part)
else:
parts.append(html.escape(str(part.value)))
return "".join(parts)
F-strings evaluate and return a str immediately -- there is no opportunity to intercept the interpolated values. T-strings are designed for exactly the cases where you need that interception: HTML escaping, SQL parameterization, i18n, structured logging.
How Does concurrent.interpreters Work?
The new concurrent.interpreters module lets you run code in isolated Python sub-interpreters within the same process. Each interpreter has its own global state, module imports, and (with the free-threaded build) its own GIL. They communicate by passing objects through channels.
from concurrent.interpreters import InterpreterPoolExecutor
def cpu_task(n):
return sum(range(n))
with InterpreterPoolExecutor(max_workers=4) as pool:
futures = [pool.submit(cpu_task, 10**7) for _ in range(4)]
results = [f.result() for f in futures]
The key difference from multiprocessing: sub-interpreters share the same process memory (no fork overhead, no serialization for primitive types), but isolation prevents module-level state from leaking between interpreters. Combined with free-threaded mode, this is the foundation for true Python-level parallelism without forking.
What Is the Tail-Call Interpreter and How Does It Speed Things Up?
The tail-call interpreter replaces the central dispatch loop with a chain of tail calls -- each opcode handler calls the next handler directly rather than returning to a central switch. On Clang 19+ (x86-64 and AArch64), this avoids the branch predictor mispredictions that plague dispatch loop patterns and lets the CPU's return stack buffer track opcode sequences more accurately.
Compile with Clang 19+ to get the tail-call interpreter (it falls back to the standard loop on other compilers). Expectation: 3-5% average speedup on pyperformance; up to 10-15% on tight bytecode-heavy loops. This stacks on top of the specializing adaptive interpreter from 3.11.
What Else Changed in Python 3.14?
Zstandard Compression (PEP 784)
The compression.zstd module adds Zstandard support: faster compression than gzip at higher ratios. The compression package now unifies gzip, bz2, lzma, zlib, and zstd under a common namespace.
UUID v6, v7, v8
The uuid module adds time-ordered UUID generation (v7 is the most useful -- monotonic, time-sortable, random low bits) and custom-layout UUIDs (v8). UUID generation for v3-v5 is also 40% faster.
Multiprocessing Start Method Change
On Linux and BSD, the default multiprocessing start method changes from fork to forkserver (macOS already used spawn since 3.8). Fork-without-exec is unsafe with threads because it copies thread state inconsistently. If your code depends on inherited state from fork, you must now explicitly set multiprocessing.set_start_method("fork").
FAQ
When should I use T-strings over f-strings?
Use T-strings whenever you need the static and dynamic parts of a string separated before final rendering -- HTML escaping, SQL query building, internationalization lookups, or structured log records. Use f-strings for all other string formatting where immediate interpolation is what you want.
Does free-threaded mode being "supported" in 3.14 mean I should use it in production?
Supported means the CPython team commits to keeping it working, fixing bugs, and accepting PRs -- not that all third-party ecosystem is ready. Check that your specific C extensions declare GIL-safety before running production workloads on free-threaded builds. NumPy 2.x, cryptography, and several other major packages have added support; smaller packages may not have.
Is UUID v7 the best UUID format to use in new applications?
For most new database-backed applications, yes. UUID v7 is time-ordered (good for index locality in B-trees), includes random bits (prevents enumeration), and is monotonically increasing within a millisecond. UUID v4 is still fine for pure randomness requirements; v7 wins when you also want sortability.
Does the multiprocessing start method change break existing code?
If you rely on child processes inheriting module-level state or open file descriptors from a fork, yes -- you will notice differences. The fix is either to make initialization explicit (pass data to the child's initializer function) or set multiprocessing.set_start_method("fork") explicitly at program startup. Code that already uses spawn-compatible patterns is unaffected.
How do I verify a Python 3.14 release with Sigstore instead of PGP?
Download the .sigstore bundle alongside the tarball, then run cosign verify-blob --bundle python-3.14.0.tar.gz.sigstore python-3.14.0.tar.gz. The cosign tool handles the cryptographic verification against the Sigstore transparency log. No GPG keys or key management needed.