Beginner
Python 3.13 landed in October 2024 as one of the most developer-experience-focused releases in recent memory. Where earlier versions focused on language semantics and library improvements, 3.13 aimed squarely at making Python more pleasant to use every day: a dramatically improved interactive REPL, clearer error messages, better type system support, and two headline features that have been in the works for years — an experimental free-threaded build that removes the GIL, and an experimental just-in-time (JIT) compiler. It’s a release that rewards upgrading even if you don’t touch any of the experimental flags.
You don’t need to understand every change to benefit from Python 3.13. The improvements that affect your daily workflow — better error messages, the new REPL, improved typing features — work out of the box. The experimental features (free-threading and JIT) are opt-in and clearly labeled. If you’re running Python 3.11 or 3.12, upgrading to 3.13 is straightforward and the payoff in developer experience is immediate.
In this guide we’ll walk through the most important changes in Python 3.13 with runnable examples for each one. We’ll cover the new interactive interpreter, improved error messages, typing improvements, free-threading and JIT, important deprecations and removals, and performance improvements. By the end you’ll know exactly what’s changed and how to take advantage of it.
Python 3.13 at a Glance
Here’s a quick rundown of the most impactful changes before diving into the details:
# python313_overview.py
import sys
print(f"Python version: {sys.version}")
print(f"GIL enabled: {sys._is_gil_enabled()}")
print(f"sys.monitoring available: {hasattr(sys, 'monitoring')}")
# New typing features: TypeVar defaults and TypeIs
from typing import TypeVar, TypeIs
T = TypeVar('T', default=int) # TypeVar with default — new in 3.13!
def is_string(val: object) -> TypeIs[str]: # TypeIs — new in 3.13!
return isinstance(val, str)
values = [42, "hello", 3.14, "world", True]
strings = [v for v in values if is_string(v)]
print(f"Strings found: {strings}")
def demo_function(x: int, y: str = "hello") -> None:
local_vars = locals()
print(f"Locals: {local_vars}")
demo_function(42, "world")
Output:
Python version: 3.13.0 (main, Oct 7 2024, 08:51:45) [GCC 11.4.0]
GIL enabled: True
sys.monitoring available: True
Strings found: ['hello', 'world']
Locals: {'x': 42, 'y': 'world'}
This overview touches most of the new APIs. Let’s explore each major area in depth.
The New Interactive Interpreter (REPL)
The most immediately visible change in Python 3.13 is the completely rewritten interactive interpreter. The old REPL, largely unchanged since Python 2, has been replaced with a new implementation featuring multi-line editing, color syntax highlighting, paste mode, and better history support. Just run python3.13 in your terminal to experience it:
# repl_features.py
# These features appear automatically in the Python 3.13 REPL.
# Multi-line editing: In the REPL, pressing Enter inside brackets
# doesn't submit immediately — you can edit the whole expression.
# Example:
# >>> def greet(
# ... name: str,
# ... greeting: str = "Hello"
# ... ) -> str:
# ... return f"{greeting}, {name}!"
# Color output: Type objects, errors, and repr() output are color-coded.
# Tracebacks show the relevant source line highlighted in red.
# New REPL commands:
# exit (no parentheses needed)
# help (improved, color-formatted)
# History across sessions is preserved
print("Python 3.13 REPL features:")
features = [
"Multi-line editing (edit expressions before submitting)",
"Color syntax highlighting",
"Automatic paste mode (no ... prefix interference)",
"Persistent history across sessions",
"help command with color formatting",
"exit/quit without parentheses",
]
for f in features:
print(f" - {f}")
Output:
Python 3.13 REPL features:
- Multi-line editing (edit expressions before submitting)
- Color syntax highlighting
- Automatic paste mode (no ... prefix interference)
- Persistent history across sessions
- help command with color formatting
- exit/quit without parentheses
The new REPL is built on the same library as IDLE and supports readline-style editing shortcuts. To disable colors or the new behavior, set PYTHON_COLORS=0 or use the -q flag.

Better Error Messages
Python 3.10 started the trend of human-friendly error messages, and 3.13 continues it. The improvements focus on NameError suggestions, ImportError disambiguation, and cleaner tracebacks:
# error_messages.py
# NameError with suggestion — Python 3.13 suggests the correct name
try:
primt("Hello") # typo: 'print'
except NameError as e:
print(f"NameError: {e}")
# Output: NameError: name 'primt' is not defined. Did you mean: 'print'?
# AttributeError suggestion for object attribute typos
class Config:
def __init__(self):
self.database_host = "localhost"
self.database_port = 5432
cfg = Config()
try:
_ = cfg.databse_host # typo: 'databse' instead of 'database'
except AttributeError as e:
print(f"AttributeError: {e}")
# Output: 'Config' object has no attribute 'databse_host'.
# Did you mean: 'database_host'?
# ImportError with typo suggestion
try:
from collections import OrderedDct # typo
except ImportError as e:
print(f"ImportError: {e}")
# Output: cannot import name 'OrderedDct' from 'collections'.
# Did you mean: 'OrderedDict'?
Output:
# py313_features.py
NameError: name 'primt' is not defined. Did you mean: 'print'?
AttributeError: 'Config' object has no attribute 'databse_host'. Did you mean: 'database_host'?
ImportError: cannot import name 'OrderedDct' from 'collections'. Did you mean: 'OrderedDict'?
The suggestions use edit distance algorithms to find the closest matching name in scope. For common one-character typos and wrong-module imports they save real debugging time.
Typing Improvements: TypeVar Defaults and TypeIs
Python 3.13 adds two long-requested typing features that make generic type annotations significantly more expressive.
TypeVar Defaults (PEP 696)
You can now give TypeVar, ParamSpec, and TypeVarTuple default values. This is particularly useful for generic classes where a type parameter has a sensible default:
# typevar_defaults.py
from typing import TypeVar, Generic
# TypeVar with a default — new in Python 3.13
T = TypeVar('T', default=str)
E = TypeVar('E', default=Exception)
class Result(Generic[T, E]):
"""A Result type that defaults to str/Exception."""
def __init__(self, value: T | None = None, error: E | None = None):
self.value = value
self.error = error
self.ok = error is None
def __repr__(self) -> str:
return f"Ok({self.value!r})" if self.ok else f"Err({self.error!r})"
# Without specifying types, defaults apply: Result[str, Exception]
ok_result: Result = Result(value="success")
err_result: Result = Result(error=ValueError("bad input"))
int_result: Result[int, RuntimeError] = Result(value=42)
print(ok_result)
print(err_result)
print(int_result)
print(f"ok_result.ok: {ok_result.ok}, err_result.ok: {err_result.ok}")
Output:
Ok('success')
Err(ValueError('bad input'))
Ok(42)
ok_result.ok: True, err_result.ok: False
TypeIs (PEP 742)
TypeIs is a new special form for type narrowing. Unlike the older TypeGuard, TypeIs also narrows the type in the else branch — if TypeIs[str] returns True, the argument is str; if it returns False, the argument is definitively not str:
# typeis_example.py
from typing import TypeIs
def is_non_empty_string(val: object) -> TypeIs[str]:
"""Returns True if val is a non-empty string."""
return isinstance(val, str) and len(val) > 0
def process_items(items: list[str | int | None]) -> dict:
strings = []
non_strings = []
for item in items:
if is_non_empty_string(item):
strings.append(item.upper()) # Type checker: item is str
else:
non_strings.append(repr(item)) # Type checker: item is int | None
return {"strings": strings, "non_strings": non_strings}
data = ["hello", 42, None, "world", 0, "", "python"]
result = process_items(data)
print("Strings:", result["strings"])
print("Non-strings:", result["non_strings"])
Output:
Strings: ['HELLO', 'WORLD', 'PYTHON']
Non-strings: ['42', 'None', '0', "''"]
Static type checkers like mypy and pyright recognize TypeIs and narrow types correctly in both branches. TypeGuard (from 3.10) only narrowed in the if branch. TypeIs is the safer, more complete alternative for type narrowing predicates.

Free-Threading and JIT Compiler (Experimental)
Python 3.13 introduces two experimental performance features — an optional free-threaded build (no GIL) and an experimental JIT compiler. Both are off by default and opt-in:
# experimental_features.py
import sys
import os
# Free-threaded build: install python3.13t
print(f"GIL currently enabled: {sys._is_gil_enabled()}")
# In the free-threaded build (python3.13t), this returns False
# JIT compiler: enable with PYTHON_JIT=1 environment variable
jit_env = os.environ.get('PYTHON_JIT', '0')
print(f"JIT env: PYTHON_JIT={jit_env}")
# The 3.13 JIT is infrastructure, not a speedup yet (2-5% at best)
# Future Python versions will build on this foundation
Output:
GIL currently enabled: True
JIT env: PYTHON_JIT=0
The JIT compiler in Python 3.13 is a copy-and-patch JIT — simpler than V8 or PyPy, but shippable as experimental infrastructure. It provides minimal speedup today (2–5% on some benchmarks, regressions on others). The free-threading story is more mature — see the companion article on free-threaded Python 3.13 for full details and benchmarks.
Other Notable Changes
Defined Behavior for locals()
PEP 667 in Python 3.13 defines the behavior of locals() in optimized scopes. It always returns a fresh snapshot — modifying the returned dict no longer affects actual local variables (this behavior was previously undefined):
# locals_behavior.py
def demonstrate_locals():
x = 10
y = 20
snap1 = locals()
x = 99 # Change x AFTER taking the snapshot
snap2 = locals()
print(f"snap1: {snap1}") # Shows x=10 (snapshot at that moment)
print(f"snap2: {snap2}") # Shows x=99 (new snapshot)
# Modifying the dict does NOT affect actual variables in 3.13
snap2['y'] = 999
print(f"After snap2['y'] = 999, actual y = {y}") # still 20
demonstrate_locals()
Output:
snap1: {'x': 10, 'y': 20}
snap2: {'x': 99, 'y': 20}
After snap2['y'] = 999, actual y = 20
Removals: PEP 594 Deprecated Modules
Python 3.13 removes 19 modules that were deprecated in 3.11. These are the most likely to affect existing code: aifc, cgi, cgitb, chunk, crypt, imghdr, mailcap, nntplib, ossaudiodev, pipes, sndhdr, sunau, telnetlib, uu, and xdrlib. If your code imports any of these, find third-party alternatives before upgrading (paramiko for telnetlib, cryptography for crypt, Pillow for imghdr).
Real-Life Example: Python 3.13 Compatibility Checker
Here’s a practical script that checks a Python project for compatibility issues before upgrading to 3.13:
# compat_check_3_13.py
import ast
import sys
from pathlib import Path
REMOVED_MODULES = {
'aifc', 'cgi', 'cgitb', 'chunk', 'crypt', 'imghdr',
'mailcap', 'nntplib', 'ossaudiodev', 'pipes',
'sndhdr', 'sunau', 'telnetlib', 'uu', 'xdrlib'
}
class CompatChecker(ast.NodeVisitor):
def __init__(self):
self.issues = []
def visit_Import(self, node: ast.Import):
for alias in node.names:
if alias.name in REMOVED_MODULES:
self.issues.append(
f"Line {node.lineno}: imports removed module '{alias.name}'"
)
self.generic_visit(node)
def visit_ImportFrom(self, node: ast.ImportFrom):
if node.module in REMOVED_MODULES:
self.issues.append(
f"Line {node.lineno}: imports from removed module '{node.module}'"
)
self.generic_visit(node)
def check_file(path: Path) -> list[str]:
try:
source = path.read_text(encoding='utf-8')
tree = ast.parse(source, filename=str(path))
checker = CompatChecker()
checker.visit(tree)
return checker.issues
except SyntaxError as e:
return [f"SyntaxError: {e}"]
def check_project(directory: str) -> None:
root = Path(directory)
py_files = [f for f in root.rglob("*.py")
if not any(p.startswith('.') or p == '__pycache__'
for p in f.parts)]
print(f"Checking {len(py_files)} files for Python 3.13 compatibility...\n")
total_issues = 0
for py_file in py_files:
issues = check_file(py_file)
if issues:
print(f" {py_file.relative_to(root)}:")
for issue in issues:
print(f" WARNING: {issue}")
total_issues += len(issues)
if total_issues == 0:
print("No compatibility issues found! Ready to upgrade.")
else:
print(f"\nTotal: {total_issues} issue(s) found. Fix before upgrading to Python 3.13.")
check_project(".")
Output (clean project):
Checking 12 files for Python 3.13 compatibility...
No compatibility issues found! Ready to upgrade.
Output (project with removed module imports):
Checking 8 files for Python 3.13 compatibility...
legacy/audio_utils.py:
WARNING: Line 3: imports removed module 'aifc'
WARNING: Line 4: imports removed module 'sunau'
web/legacy_cgi.py:
WARNING: Line 1: imports removed module 'cgi'
Total: 3 issue(s) found. Fix before upgrading to Python 3.13.
Run this against your project before upgrading. After fixing import issues, also run your test suite with python -W all to catch deprecation warnings that reveal subtler compatibility problems.
Frequently Asked Questions
Should I upgrade to Python 3.13 now?
Yes, for most projects. Python 3.13 is in full bugfix support and the new REPL, better error messages, and typing improvements work immediately. The main reasons to wait are dependency compatibility (verify your key third-party libraries have 3.13 wheels) and removed modules (scan imports with the checker above). If you use any PEP 594 modules, fix those first.
Should I enable the JIT compiler?
Not for production code. The JIT in Python 3.13 is experimental infrastructure — it provides minimal speedup on most benchmarks (2–5% at best), and some workloads run slightly slower. Keep PYTHON_JIT=0 (the default) for production until the JIT matures in 3.14 and 3.15. If you want to experiment, enable it in development and benchmark your specific workload.
Does upgrading to Python 3.13 give me free-threading automatically?
No. The standard Python 3.13 build still has the GIL enabled. Free-threading is a separate build variant (python3.13t) that you install explicitly. This is intentional — the free-threaded build has slightly slower single-threaded performance and not all C extensions are compatible yet. See the companion article on free-threaded Python 3.13 for installation and benchmark details.
Do I need to update my TypeGuard usage to TypeIs?
TypeGuard is not removed and remains valid. TypeIs is the newer, more precise alternative that also narrows the else branch. For new type guard functions, prefer TypeIs. For existing TypeGuard functions, migration is optional but recommended for better type narrowing accuracy.
Is Python 3.13 faster than 3.12?
Modestly — roughly 2–5% improvement on typical Python workloads. The main performance story in 3.13 is infrastructure: the JIT framework and free-threading are the foundations for more dramatic speedups in 3.14 and beyond. If you need significant performance improvements today, the free-threaded build for multi-threaded CPU workloads is the most impactful change available.
Conclusion
Python 3.13 delivers real, immediate improvements in daily developer experience: the new REPL with multi-line editing and syntax highlighting, better NameError and AttributeError suggestions, TypeVar defaults for cleaner generic code, and TypeIs for correct two-branch type narrowing. Under the hood, the experimental free-threaded build and JIT compiler lay the groundwork for the performance improvements coming in 3.14 and 3.15.
We covered the new REPL, improved error messages, TypeVar defaults and TypeIs, the experimental free-threading and JIT features, defined locals() behavior, and the PEP 594 module removals with a compatibility checker script. Upgrading to Python 3.13 is recommended for most projects — scan imports for removed modules, then enjoy the improved interactive experience.
Full details in the official Python 3.13 What’s New documentation.