Python Startup Optimization — Core Concepts
Why startup time matters
Python’s startup time — the time between executing python script.py and your first line of code running — is critical for CLI tools, serverless functions, microservices, and any application where cold starts affect user experience. A typical Python 3.11 startup (without imports) takes about 15–25ms. Add common libraries and it can exceed 500ms.
Where the time goes
Python startup has several phases, each contributing to total latency:
- Interpreter initialization (~10ms) — CPython initializes its internal data structures, sets up the GIL, creates built-in types
- Site module (~5–15ms) — processes site-packages,
.pthfiles,sitecustomize.py - Module imports (variable, often dominant) — importing your code and its dependencies
Profiling startup time
The most important tool is -X importtime, available since Python 3.7:
python -X importtime my_script.py 2> import_times.txt
This prints a tree showing cumulative and self time for every import:
import time: self [us] | cumulative | imported package
import time: 342 | 342 | _io
import time: 127 | 127 | marshal
import time: 1205 | 1674 | _frozen_importlib_external
import time: 45123 | 45123 | numpy
Sort by cumulative time to find the biggest offenders:
python -X importtime -c "import my_module" 2>&1 | sort -t'|' -k2 -rn | head -20
Lazy imports
The highest-impact optimization is deferring expensive imports until they are actually needed:
# Before: loads pandas at startup even if not always used
import pandas as pd
def analyze(data):
return pd.DataFrame(data).describe()
# After: only loads pandas when analyze() is called
def analyze(data):
import pandas as pd
return pd.DataFrame(data).describe()
Python caches imports in sys.modules, so the second call to analyze() does not re-import pandas.
Structured lazy imports
For larger applications, use a lazy import pattern:
import importlib
def _lazy_import(name):
"""Import a module lazily — only when first accessed."""
return importlib.import_module(name)
class LazyModule:
def __init__(self, name):
self._name = name
self._module = None
def __getattr__(self, attr):
if self._module is None:
self._module = importlib.import_module(self._name)
return getattr(self._module, attr)
# Use as a module-level stand-in
pd = LazyModule("pandas")
np = LazyModule("numpy")
Python 3.12+ has experimental support for lazy imports via importlib.util.LazyLoader.
Reducing import chains
A single import requests triggers importing dozens of sub-modules: urllib3, certifi, charset_normalizer, idna, and their dependencies. If you only need to make a simple HTTP GET request and urllib.request suffices, you save hundreds of milliseconds.
Review your dependency tree:
# See what importing a module actually pulls in
python -X importtime -c "import requests" 2>&1 | wc -l
# Often 80+ modules for a single top-level import
The -S flag
Skipping the site module with python -S removes 5–15ms of startup. This is useful for scripts that do not need pip-installed packages:
python -S -c "print('fast')"
Bytecode caching (.pyc files)
Python compiles .py files to bytecode and caches them in __pycache__/ directories. The first run is slower (compilation needed); subsequent runs use the cache. Ensure your deployment preserves .pyc files:
# Pre-compile all .py files
python -m compileall /path/to/your/app
In Docker images, run compileall during the build step to avoid compilation overhead at runtime.
Python 3.11+ improvements
CPython has been actively reducing startup time:
- Python 3.11 — frozen standard library modules reduce import overhead
- Python 3.12 — further frozen module optimizations
- Python 3.13 — experimental free-threading changes may affect startup
Frozen modules are pre-compiled into the interpreter binary itself, bypassing filesystem reads entirely.
Common misconception
People often think python -O (optimize flag) speeds up startup significantly. It removes assert statements and __debug__ code but has negligible effect on startup time. The performance difference is typically under 1%.
The one thing to remember: Profile with -X importtime to find the imports that dominate your startup, then defer them with lazy loading or replace heavyweight dependencies with lighter alternatives — most Python startup optimization comes down to importing less.
See Also
- Python Ast Module Code Analysis How Python's ast module reads your code like a grammar teacher diagrams sentences — turning source text into a tree you can inspect and change.
- Python Dis Module Bytecode How Python's dis module lets you peek at the secret instructions your computer actually runs when it executes your Python code.
- Python Gc Module Internals How Python's garbage collector automatically cleans up memory you are no longer using — like a tidy roommate for your program.
- Python Importlib Custom Loaders How Python's importlib lets you teach Python to load code from anywhere — databases, zip files, the internet, or even generated on the fly.
- Python Site Customization How Python's site module sets up your environment before your code even starts running — the invisible first step of every Python program.