What's New in Python 3.14: t-strings, Deferred Annotations, Official Free-Threading
Python 3.14 shipped in October 2025. It arrived with the obligatory pi-version jokes (3.14), but the contents are substantial: one new string syntax, the conclusion to the long-running annotations saga, and the formalization of free-threading that changes the direction of Python concurrency. This post covers the 3.14 changes in order of practical impact.
t-strings: they look like f-strings, but they are not strings (PEP 750) #
The most visible new syntax. Put t in front instead of f, and instead of becoming a string immediately, you get a Template object.
name = "world"
greeting_f = f"hello, {name}" # str: "hello, world"
greeting_t = t"hello, {name}" # Template object: not a string yet
# a Template holds static text and interpolated values still separated
for part in greeting_t:
print(repr(part))
# 'hello, '
# Interpolation(value='world', expression='name', ...)The point is that the consumer decides how the interpolated values are handled. With f-strings, values melt into the string instantly, leaving nowhere to insert HTML escaping or SQL binding. With t-strings, values arrive still separated, so a library can escape just the values or turn them into parameters.
def safe_html(template) -> str:
parts = []
for item in template:
if isinstance(item, str):
parts.append(item) # static text passes through
else:
parts.append(escape(str(item.value))) # only interpolated values get escaped
return "".join(parts)
user_input = "<script>alert('x')</script>"
print(safe_html(t"<p>{user_input}</p>"))
# <p><script>alert('x')</script></p>As the structural fix for f-string injection, libraries for HTML templating, SQL, log masking, and prompt assembly are being rebuilt on top of it. You will rarely reach for t-strings directly in application code yet, but once libraries start accepting them, “strings that mix in user input are t-strings” is likely to become the new convention.
Deferred annotation evaluation becomes the default (PEP 649/749) #
Until now, type hints were evaluated eagerly at function definition time. Reference a class not yet defined and you got a NameError — which is why wrapping annotations in strings ("User") and putting from __future__ import annotations at the top of files became widespread habits.
From 3.14, annotations are evaluated when needed, by default.
class Node:
# up to 3.13: NameError, or the string "Node" required
# from 3.14: works as-is
def next_node(self) -> Node | None: ...- Code that wrapped annotations in strings because of circular references no longer needs to.
from __future__ import annotationsstill works, but new code no longer needs it.- Code that reads annotations at runtime (libraries like Pydantic and FastAPI) has been standardized through the new
annotationlibmodule. The major libraries have already adapted, so as an application developer, keeping dependencies current is all it takes.
The free-threaded (no-GIL) build is officially supported (PEP 779) #
The free-threaded build that arrived experimentally in 3.13 sheds its experimental label in 3.14 and moves to officially supported status. In a Python without the GIL (global interpreter lock), multiple threads genuinely execute Python code on multiple cores at once.
Its realistic position needs stating precisely.
- It is still a separate build (
python3.14t). The default Python keeps the GIL, and the two builds differ in C extension compatibility. - Single-threaded performance is somewhat slower than the GIL build. The gap shrank considerably versus 3.13, but it is not zero.
- The C extension ecosystem (NumPy and other major packages) is in the middle of shipping free-threading-compatible wheels, and that support decides whether adoption is feasible.
The judgment at this point summarizes as: workloads that genuinely need CPU parallelism are well worth validating on the free-threaded build; ordinary web services have no reason to rush. Web app parallelism remains process-based (workers) as the standard. If processes versus threads is fuzzy, see The Difference Between Processes and Threads.
In the same vein, multiple interpreters landed in the standard library (PEP 734, concurrent.interpreters). Several interpreters, each with its own GIL, run inside one process — a middle point lighter than processes and more isolated than threads. concurrent.futures.InterpreterPoolExecutor exposes it through the familiar Executor interface.
Small but welcome changes #
- Parenthesis-free except (PEP 758): catching multiple exceptions can now be written
except ValueError, TypeError:instead ofexcept (ValueError, TypeError):. Parentheses are still required withas. - Zstandard in the standard library (PEP 784): the
compression.zstdmodule arrives. zstd — faster and tighter than gzip — with no third-party dependency. - Remote debugging interface (PEP 768): an official, safe channel for attaching to a running Python process from outside, with
pdbsupport. There is now a standard answer to “I need to see inside a production process I cannot stop.” The profiling side of the same problem was covered with py-spy. - Better error messages: suggestions for mistyped keyword arguments, clearer guidance when strings and numbers get mixed incorrectly, and more. Debugging improves just by upgrading.
- UUID v6, v7, v8 support: the
uuidmodule now covers the recent specifications. UUIDv7, which sorts by time, had pent-up demand for database primary keys in particular.
The upgrade call #
3.14 is a release with almost no syntax-breaking changes, so the upgrade barrier is low. The order never changes: confirm that dependency wheels exist for 3.14, add 3.14 to CI and get tests passing, then raise the runtime. With uv, the flow is uv python install 3.14 to fetch the version and a lock-file regeneration to validate. Managing multiple versions side by side is covered in Python Packaging #4.
Summary #
- t-strings produce template objects with interpolated values kept separate, letting libraries handle escaping and binding — the structural fix for injection.
- Deferred annotation evaluation is now the default, retiring string-wrapped annotations and
from __future__ import annotations. - The free-threaded build is officially supported. CPU-parallel workloads deserve a validation run; ordinary web services stay on process parallelism for now.
- Multiple interpreters, zstd compression, remote debugging, and UUIDv7 thicken the standard library’s practical toolbox.
- With few breaking changes, upgrade in the usual order: dependency wheels → CI → runtime.