Every Python developer eventually learns this lesson the hard way: install enough packages globally and sooner or later two projects need conflicting versions of the same library, and everything breaks. Python virtual environments are the fix, and they are simple enough that there is no excuse not to use one for every project.
What a virtual environment actually is
A virtual environment is just an isolated folder containing its own Python interpreter and its own set of installed packages. When it is active, pip install puts packages there instead of in your system Python. Delete the folder and the project’s dependencies vanish cleanly, with nothing left polluting the rest of your machine.
Creating and activating one
Python ships with venv built in, so you need nothing extra:
# Create it (the second "venv" is just the folder name)
python -m venv venv
# Activate it
source venv/bin/activate # macOS / Linux
venv\Scripts\activate # Windows
# Your prompt now shows (venv)
pip install requests
While it is active, python and pip point at the isolated environment. When you are done, type deactivate to step back out.
Locking your dependencies
An environment is only reproducible if you record what is in it. That is what a requirements file is for:
# Save the current packages
pip freeze > requirements.txt
# Recreate them elsewhere
pip install -r requirements.txt
Commit requirements.txt to your repository, but never commit the venv folder itself — add it to .gitignore. The folder is large, machine-specific, and trivially rebuilt from the requirements file.
Why it matters more than it seems
Beyond avoiding version clashes, virtual environments make your projects portable and your bugs reproducible. When a teammate can recreate your exact dependency set in one command, “works on my machine” stops being an argument. And when a deployment uses the same locked versions as your laptop, a whole category of surprise failures disappears.
A quick word on the alternatives
You will hear about tools like virtualenv, pipenv, poetry, and conda. They add features — nicer dependency resolution, lock files, environment management — but they all rest on the same core idea you just learned. Start with the built-in venv, get comfortable, and graduate to a heavier tool only when a real need appears.
The problems you’ll actually hit (and their fixes)
Virtual environments are simple, but a few stumbles are so common they’re practically rites of passage. Knowing them in advance saves the frustrating hour each one usually costs:
- “I installed the package but Python can’t find it.” Nine times out of ten, the environment wasn’t active when you ran
pip install— the package went to system Python. Check your prompt for the(venv)prefix, or runwhich python(Windows:where python) and confirm it points inside your project folder. - “It works in the terminal but not in my editor.” Your editor runs its own interpreter setting. In VS Code, run “Python: Select Interpreter” and pick the one inside
venv/; PyCharm has the same under project settings. Editor and terminal disagreeing about which Python they’re using is the classic source of phantom import errors. - “I moved/renamed the project and the venv broke.” Environments hard-code absolute paths inside their activation scripts, so they don’t survive being moved. Don’t fix it — delete the folder and rebuild from
requirements.txtin thirty seconds. This is exactly why the requirements file, not the venv folder, is the source of truth. - “Activation is blocked on Windows.” PowerShell’s execution policy blocks the activation script by default. Run
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUseronce, and it works from then on.
Keeping requirements.txt honest
A subtle problem with pip freeze: it dumps everything in the environment, including dependencies-of-dependencies. Your file ends up listing forty packages when your project really depends on four, and nobody can tell which is which anymore. The cleaner habit is to maintain the top-level list by hand — literally just the packages you import — and let pip resolve the rest at install time:
# requirements.txt — just what YOU depend on
requests>=2.31
click>=8.1
python-dotenv
For projects that need exact reproducibility (deployments especially), keep both: a hand-written requirements.txt of direct dependencies, and a frozen requirements.lock generated by pip freeze for installers to use. That two-file pattern is essentially what heavier tools like Poetry automate — understanding it manually first makes those tools make sense later.
One environment per project, always — even for “quick scripts”
The temptation to skip the venv “just for this little script” is exactly how global Python rots. That little script installs an old version of a library, which silently downgrades a dependency your other project relies on, and three weeks later you’re debugging an error that makes no sense. Modern Linux distributions have gotten so opinionated about this that many now refuse pip install outside a venv entirely (the “externally-managed-environment” error you may have met on Ubuntu or Debian). The ten-second habit — python -m venv venv && source venv/bin/activate — is cheaper than even one such debugging session. Make it muscle memory, the same reflex as git init.
Frequently asked questions
Should the folder be called venv, .venv, or something else? Convention has largely settled on venv or .venv (the dot hides it in file listings, and tools like VS Code auto-detect both). What matters is consistency across your projects and that it’s in .gitignore.
How do I upgrade Python inside a venv? You don’t, really — a venv is bound to the interpreter that created it. Install the new Python, delete the venv, recreate it with the new version, reinstall from requirements. Because rebuilding is cheap, this is a two-minute operation rather than a migration.
Do I need venvs inside Docker containers? Opinions differ, since the container itself is isolation. Skipping the venv in a container is defensible; many teams still use one to keep tooling behavior identical inside and outside Docker. Either is fine — just be consistent.
Make creating a virtual environment the first thing you do in any new project. It takes ten seconds and saves you hours of dependency archaeology later. Python virtual environments are not advanced — they are table stakes for writing Python you can trust.

