The error looks like a contradiction. You ran pip install requests, the install printed Successfully installed requests-2.31.0, and the very next python my_script.py threw ModuleNotFoundError: No module named 'requests'. Both can't be right. They are: pip installed into one Python and your script ran in a different Python. The whole class of bug comes down to a mismatch between two paths, and three terminal commands will identify which mismatch you have. This guide walks the diagnostic in order, then explains the five specific bug shapes and how sys.path decides what Python actually finds. It's one of 10 explainers in our Python Environment Setup pillar page.

Key Takeaways

  • The bug is almost always pip-and-python pointing at different interpreters. Use python -m pip install <pkg> instead of plain pip install to force them to match.
  • Three commands diagnose 95% of cases: which python, which pip, and python -c "import sys; print(sys.path)". If the first two paths don't share a parent directory, you've found the bug.
  • Five bug shapes cover everything: wrong env, wrong Python, not actually installed, wrong import name, editable install path missing.
  • sys.path is the source of truth. Python searches that list in order; the first match wins. If your package directory isn't on it, the import fails.
  • Don't reach for PYTHONPATH as a quick fix. Use a virtual environment instead, scoped to the one project that needs it.
ModuleNotFoundError diagnostic flowchart ModuleNotFoundError after pip install Step 1: which python & which pip Do they share a parent directory? No (mismatch) Yes (same env) BUG #1 or #2 Wrong env or wrong Python. Use python -m pip install Step 2: pip show pkg Is it actually installed? No Yes BUG #3 Reinstall: python -m pip install pkg Step 3: import name Does it match package name? (beautifulsoup4 → import bs4) If name wrong: BUG #4. If still failing: Step 4: check sys.path → BUG #5 (editable install) or stale __pycache__
Run the three diagnostic commands in order. Each branch fans out into one of the five bug shapes.

The Three-Command Diagnostic

Before reading anything else, run these three commands in the same terminal where you got the error.
# 1. Which Python is on your PATH?
which python      # macOS/Linux
where python      # Windows

# 2. Which pip is on your PATH?
which pip
where pip

# 3. What does the running Python actually search?
python -c "import sys; print('\n'.join(sys.path))"
Outputs to compare:
  • If which python and which pip show paths that do not share a parent directory, the package landed in a different Python. That's Bug #1 (wrong env) or Bug #2 (wrong Python version).
  • If they match but the package still won't import, list what's installed in that Python with pip show <package>. If it returns "WARNING: Package(s) not found", that's Bug #3 (not actually installed; pip silently went to a different env or failed).
  • If pip show finds it but your import still fails, you're either using the wrong import name (Bug #4) or hitting an editable-install path problem (Bug #5).
That's it. Three commands, one diagnostic, five outcomes. The rest of this guide walks each bug shape with the exact terminal output you'll see and the exact fix.Person debugging code at a laptop with terminal output visible, evoking the diagnostic process for Python import errors

Bug #1: Wrong Environment Active (or None Active)

The most common cause by a wide margin. You created a venv, ran pip install inside it, then opened a new terminal and forgot to activate the venv before running the script.
$ which python
/usr/bin/python3              # ← system Python, NOT your venv

$ which pip
/Users/jesse/proj/.venv/bin/pip   # ← venv pip

$ python my_script.py
ModuleNotFoundError: No module named 'requests'
The pip on PATH was the venv's; the python on PATH was the system's. The install landed in the venv; the script ran in system Python. Both look right in isolation; the bug is the gap between them. The fix is to activate the venv before running anything:
source .venv/bin/activate     # macOS/Linux
.venv\Scripts\activate        # Windows PowerShell

# Verify after activation:
$ which python
/Users/jesse/proj/.venv/bin/python   # ← now matches pip

$ python my_script.py
# works
For the underlying mechanics of why activation matters, see our venv vs uv vs Poetry decision guide. The two-line answer: activation prepends the venv's bin/ directory to $PATH so python and pip both point at the same install.
Modern shortcut. uv and Poetry both skip activation entirely. uv run my_script.py and poetry run python my_script.py set the PATH for the duration of one command, which sidesteps the entire "did I activate" question. If you keep hitting this bug, switching to uv run will end it.

Bug #2: Wrong Python Version

You have Python 3.9 and Python 3.12 installed. You typed pip install pandas and pip installed for Python 3.9. Then you ran python3.12 my_script.py. The script ran in 3.12, which doesn't have pandas.
$ which pip
/usr/local/bin/pip            # bound to /usr/local/bin/python (3.9)

$ which python3.12
/opt/homebrew/bin/python3.12   # different directory tree

$ python3.12 -c "import pandas"
ModuleNotFoundError: No module named 'pandas'
The fix is to install into the specific Python you're running. Always do this with python -m pip, not plain pip:
# GOOD: install lands wherever python3.12 is
python3.12 -m pip install pandas

# DANGEROUS: depends on PATH order, may land anywhere
pip install pandas
For the multiple-Python case in general, see running multiple Pythons on one machine. The principle: every time you call pip bare, you're trusting your PATH. python -m pip never trusts PATH; it asks the specific interpreter to find its own pip.

Bug #3: Not Actually Installed

pip can return success and not install the package. Most often this happens silently because the wheel was for a Python version you don't have, the install was cached but never extracted, or pip aborted partway through a dependency conflict and rolled back without printing a clear error.
$ python -m pip install some_package
# ... output that looks successful ...

$ pip show some_package
WARNING: Package(s) not found: some_package

$ python -m pip install some_package --force-reinstall
# now look carefully at the output for ERROR or WARNING lines
The diagnostic is pip show. It reports the installed location, the version, and the deps; if it returns "not found", the package truly isn't there regardless of what the install command claimed. Re-run with --force-reinstall and read every line; if any wheel failed to build (common with native deps), you'll see ERROR: Failed building wheel for <dep> in the middle of the output.

Bug #4: Wrong Import Name

The pip package name and the Python import name are often different. pip install beautifulsoup4 installs a package you import as bs4. pip install Pillow installs PIL. pip install python-dateutil installs dateutil. pip install scikit-learn installs sklearn.
$ pip show beautifulsoup4
Name: beautifulsoup4
Version: 4.12.3
Location: /Users/jesse/proj/.venv/lib/python3.12/site-packages
Files: bs4/...                ← THIS is the import name
The Files: line of pip show -f <pkg> reveals the actual top-level package directories the wheel ships. That's what you import. The pip name and the import name are not connected by convention; you have to look. Here are the famous mismatches:
pip install ...import ...
beautifulsoup4bs4
PillowPIL
python-dateutildateutil
scikit-learnsklearn
PyYAMLyaml
opencv-pythoncv2
pycryptodomeCrypto
msgpack-pythonmsgpack

Bug #5: Editable Install or Local Package Path Missing

You ran pip install -e . on your project and the import works in one terminal but not another, or works for one Python version but not another, or works until you rename a directory.
$ pip install -e .
Successfully installed myproject-0.1.0

$ python -c "import myproject"
ModuleNotFoundError: No module named 'myproject'
Editable installs (pip install -e .) work by adding a .pth file to site-packages that points at your source directory. If the path in that .pth file goes stale (you renamed the project folder, you moved it to a different drive, the venv was on a network share that's no longer mounted), the import silently fails.
# Find the .pth file:
$ python -c "import site; print(site.getsitepackages())"
['/Users/jesse/proj/.venv/lib/python3.12/site-packages']

$ ls /Users/jesse/proj/.venv/lib/python3.12/site-packages | grep .pth
__editable__.myproject-0.1.0.pth

$ cat /Users/jesse/proj/.venv/lib/python3.12/site-packages/__editable__.myproject-0.1.0.pth
/Users/jesse/projects/myproject/src     # ← this path needs to exist
If that path doesn't exist or doesn't have the package, the import fails. Re-run pip install -e . from the project root to rewrite the .pth file. For packaging your project to import correctly in the first place, see importing from another folder.

How sys.path Actually Works

Python's import requests walks sys.path in order. The first directory that contains requests.py or requests/__init__.py wins; everything past that point is invisible to the import. sys.path is built at startup from four sources (PSF docs):
  1. The directory of the script being run. (Or the current directory if you ran the interactive REPL.) This is why importing from a sibling file just works.
  2. PYTHONPATH if set. A colon-separated (semicolon on Windows) list of extra directories.
  3. The installation default. The interpreter's stdlib directory.
  4. site-packages via the site module: the directory where pip drops third-party packages, plus user-site if enabled.
The diagnostic command from earlier:
$ python -c "import sys; print('\n'.join(sys.path))"
/Users/jesse/proj
/usr/local/Cellar/python@3.12/3.12.1/Frameworks/Python.framework/Versions/3.12/lib/python312.zip
/usr/local/Cellar/python@3.12/3.12.1/Frameworks/Python.framework/Versions/3.12/lib/python3.12
/usr/local/Cellar/python@3.12/3.12.1/Frameworks/Python.framework/Versions/3.12/lib/python3.12/lib-dynload
/usr/local/lib/python3.12/site-packages
If the directory that contains your missing package isn't in that list, the import will fail no matter what pip says. Conversely, if the package IS in sys.path but the import still fails, the error message will be different (usually ImportError with a description of what broke during import).

PYTHONPATH: Useful, but Almost Never the Right Fix

You'll find advice online to set PYTHONPATH as a quick fix for ModuleNotFoundError. It works in the moment and then poisons every future Python process you run. A better tool exists in nearly every case:
Use caseRight tool
Missing third-party packagepython -m pip install <pkg> in a venv
Importing your own package without installingpip install -e . (editable install)
One-off script needs a sibling directory on pathsys.path.insert(0, ...) inside the script
Integrating with another build systemPYTHONPATH (this is the legitimate case)
The "right" use of PYTHONPATH is integrating Python into a build system that already manages paths (Bazel, certain Docker setups). For "I just installed something and it won't import", it's almost always the wrong tool.

The Stale __pycache__ Trap

Python caches compiled bytecode in __pycache__/ directories next to your source files. Usually this is invisible and helpful. Occasionally it bites:
  • You renamed a module file (utils.py to helpers.py) but the old __pycache__/utils.cpython-312.pyc is still there and gets picked up first.
  • You switched Python versions (3.11 to 3.12) and the cached .pyc is for the wrong version, which can confuse imports for compiled extension modules.
  • You moved your project between machines and stale paths in the cache reference old locations.
The clean nuke:
# macOS/Linux:
find . -type d -name __pycache__ -exec rm -rf {} +
find . -name "*.pyc" -delete

# Windows PowerShell:
Get-ChildItem -Path . -Recurse -Filter "__pycache__" | Remove-Item -Recurse -Force
Get-ChildItem -Path . -Recurse -Filter "*.pyc" | Remove-Item -Force
This is a low-cost, low-risk thing to try when imports behave strangely after a rename or refactor.

IDE Interpreter Settings: VS Code and PyCharm

Half of all "but it works in the terminal" reports come from the IDE running a different Python than the terminal does. Both major IDEs ship a Python interpreter picker.

VS Code

Open the command palette (Ctrl+Shift+P / Cmd+Shift+P) and run Python: Select Interpreter. Pick the venv interpreter explicitly (it'll be listed as something like .venv/bin/python (Python 3.12.1)). The selection is stored in .vscode/settings.json as python.defaultInterpreterPath. If you have multiple venvs, the wrong one being selected is the most common source of "it works in the terminal".

PyCharm

Open File → Settings → Project: <name> → Python Interpreter. The dropdown lists every interpreter PyCharm has detected; click the gear icon to add an existing venv by pointing at its python binary. PyCharm stores this per-project, so each project should point at its own venv.

The Diagnostic Cheat Sheet

Print this and keep it next to your monitor:
SymptomRun thisLook for
"No module named 'foo'"which python; which pipSame parent dir?
Same env, still failingpip show foo"Package(s) not found" → not installed
pip show foo works but import failspip show -f fooTop-level dir name = import name
Editable install acting weirdcat .venv/lib/.../*.pthPath exists and points where expected?
Behavior changed after refactordelete __pycache__Stale .pyc files
Works in terminal, fails in IDEIDE interpreter pickerWrong Python selected

Frequently Asked Questions

Why does pip install succeed but Python still says ModuleNotFoundError?

Almost always: pip installed the package into one Python and you're running a different Python. The pip command and the python command on your PATH can point at different interpreters, especially on machines with multiple Python versions or when a virtual environment isn't activated. The fix is to make them line up. Run python -m pip install <package> instead of plain pip, because python -m pip guarantees pip is using the same interpreter you'll run your code with. To verify which is which, run which python and which pip (or where python and where pip on Windows); if they don't share a parent directory, you've found the bug.

What is the difference between pip install and python -m pip install?

The pip command is a standalone executable whose location is decided by your PATH variable. python -m pip is the same pip package, but launched as a module of the specific Python you're invoking, which guarantees the install lands in that Python's site-packages directory. The two can diverge: a global pip on PATH might install to system Python while you're inside an activated venv whose python points elsewhere. python -m pip eliminates the ambiguity, which is why the Python Packaging Authority recommends it as the default form.

What is sys.path in Python and how does it relate to import errors?

sys.path is a list of directories Python searches when you write import foo. The first matching foo.py or foo/__init__.py wins; if nothing matches, you get ModuleNotFoundError. The list is built at interpreter startup from the directory of the script you ran, the PYTHONPATH environment variable (if set), and the standard library and site-packages directories of whichever Python you launched. When a package looks installed but won't import, the fastest diagnostic is to print sys.path and check whether the directory that contains the package is actually on it.

What does ModuleNotFoundError: No module named 'pip' mean?

Your Python install doesn't have pip available as a module. This usually happens when pip itself was uninstalled (rare), when a fresh Python install skipped pip (most common on Linux distros that strip pip out), or when you're invoking the wrong Python (pip is in a different interpreter you're not running). The bootstrap fix is python -m ensurepip --upgrade on Linux and macOS or py -m ensurepip --upgrade on Windows. If that doesn't work, your Python install is broken and you should reinstall it, ideally inside a fresh venv.

Should I use PYTHONPATH to fix ModuleNotFoundError?

Almost never. PYTHONPATH is a blunt instrument: setting it adds directories to every Python you run on the system, including ones where you didn't intend the override. The right tool for missing-package errors is a virtual environment plus pip install, which scopes the package to the project that needs it. PYTHONPATH has legitimate uses (developing a package without installing it, integrating Python with another build system) but reaching for it as a quick fix to ModuleNotFoundError almost always trades one error for a worse, harder-to-debug one later.

The Bottom Line: Same Python, Same Pip, Same Path

The whole class of bug is a path mismatch. Three commands diagnose it (which python, which pip, pip show <pkg>), five bug shapes account for everything (wrong env, wrong Python, not actually installed, wrong import name, editable install), and sys.path is the source of truth Python actually uses. Make the pip and python interpreters line up, use python -m pip as a habit, and reach for a venv before you reach for PYTHONPATH. For the broader environment story, browse the Python Environment Setup pillar page or jump to running multiple Pythons on one machine if Bug #2 is your real problem.

Code Without the Path Confusion

CodeGym's Python track runs Python in your browser through its WebIDE. No pip, no venv, no PATH gymnastics. 800+ hands-on tasks across 62 levels, an AI validator that grades every submission in seconds, and an AI mentor that explains what broke when an error shows up. First level free; full plan on the pricing page. Learn Python on the free track →