What is the difference between a src layout and a flat layout in a Python project?
answer
- Where the package directory sits
- Repository root versus a src/ directory
- What start-up adds to sys.path
- Importable straight from a checkout, or not
- src is never itself a package
basics
~20 sA flat layout keeps the importable package directory at the repository root, beside pyproject.toml. A src layout moves that directory under src/, so nothing at the root is importable and the code must be installed before it can be imported.
solid answer
~40 sIn a flat layout the package folder, say `indexer/`, sits at the top of the repository next to `pyproject.toml` and `tests/`. In a src layout the same folder lives at `src/indexer/`, and `src` itself is not a package: it has no `__init__.py` and never appears on `sys.path`. That single move is the whole argument. CPython puts the script's directory (or the working directory for `python -m` and `python -c`) at the front of `sys.path`, so a flat-layout checkout lets `import indexer` succeed straight from the working copy even when nothing was ever installed, while a src layout forces that import to resolve through the installed distribution in site-packages. The built wheel is identical either way: it ships `indexer/`, never `src/`.
code
console · 1 linepython -c "import importlib.util; print(importlib.util.find_spec('src'))"go deeper
Recall the two shapes and the import name: a package at the repo root versus the same package under src/, imported as indexer in both cases. Be able to say that under src layout you must install the project before you can import it.
Explain the mechanism, not just the shape: start-up puts the script or working directory first on sys.path, which is why a flat-layout checkout imports without an install and a src layout does not. Note that the built wheel is identical either way.
Show you decide by consequence. Say which repositories in your world publish a distribution and therefore get src, which are deployed from a checkout and keep flat, and what concrete failure the guarantee has caught for you.
Own it as a standard: one default layout across the organisation's libraries, a template that bakes it in, and a clear carve-out for application repositories, so the choice is never re-argued per project and never blocks a newcomer's first run.
## The two shapes Both layouts describe the same project — one importable package, some tests, one `pyproject.toml`. They differ only in where the package directory sits. ```text flat layout src layout . . ├── pyproject.toml ├── pyproject.toml ├── indexer/ ├── src/ │ ├── __init__.py │ └── indexer/ │ └── rebuild.py │ ├── __init__.py └── tests/ │ └── rebuild.py └── test_rebuild.py └── tests/ └── test_rebuild.py ``` The import name is `indexer` in both. `src` is not a package and is never part of any import statement: it has no `__init__.py`, it is not added to `sys.path`, and the build backend strips it when it produces the wheel. A user who installs your distribution sees exactly the same thing either way. ## Why the position matters at all Python resolves `import indexer` by walking `sys.path` in order and taking the first hit. At interpreter start-up CPython prepends one entry to that list: for `python script.py` it is the directory holding the script, and for `python -m module`, `python -c "..."` and the interactive prompt it is the current working directory. In a flat-layout checkout, the repository root *is* that directory and the package lives directly in it, so `import indexer` finds the working copy before it ever reaches site-packages. In a src layout the root holds only `src/`, `tests/` and configuration; none of those is the package, so the import falls through to whatever the environment actually has installed. This is why the choice is not decoration. It decides a single question: **when you run your code during development, are you running the source tree, or the artefact you are going to ship?** ## What each choice buys *Flat layout* is simpler and immediately usable. Clone, run, and it imports — no install step, no environment to prepare, no confusion about why the module "does not exist yet". For an application you deploy from a checkout or an image, for a script, for a teaching example, that is exactly right. There is no wheel whose contents could disagree with the tree, so nothing is being hidden. *src layout* buys one guarantee: your development commands cannot import a module that is not part of the installed distribution. Every consequence follows from that guarantee. A new submodule that the build never picked up, a subpackage missing its `__init__.py`, a data file the backend did not include, an `__init__` re-export you forgot to add — under a src layout, the first run against the installed package fails, on your machine, in seconds. Under a flat layout the same mistake sails through every local run and every test job and only surfaces when a user installs the wheel. There is a second, quieter benefit. With the package out of the root, "the thing that is importable" and "the repository" stop being the same directory, so stray top-level names — a `utils.py` or a `tests/` folder at the root — cannot be mistaken for library modules by a human or by a discovery algorithm. ## The cost, honestly You must install the project before you can run anything against it, usually as an editable install into a virtual environment. That is one extra step for a newcomer, and one more thing to get wrong. Editors and debuggers occasionally need a nudge to find the interpreter that has the package installed. Ad-hoc `python somescript.py` at the root no longer works without the install. Teams that find the ceremony unhelpful for a service repository are not doing anything wrong. ## How to decide The rule of thumb that survives contact with real projects: **if you publish a distribution, use src; if you only ever run from a checkout, flat is fine.** A library on a package index, an internal library that other repositories install, anything with a version number that someone else depends on — those want the src layout, because the failure it prevents (shipping a wheel that does not contain what you tested) is exactly the failure that reaches other people. An application built into a container image, a data pipeline run from its own repository, a single-file tool — those get little from it. Two things the layout does *not* do, and that candidates often over-claim. It does not change the import name, and it does not change the wheel's contents; both layouts produce a wheel containing `indexer/`. And it does not make tests importable or un-importable by itself — tests normally live outside the package in both layouts, and how a test run finds them is a separate question from how it finds the library.
- Does choosing a src layout change the wheel your users install?No. The wheel contains the package itself — `indexer/` — never a top-level `src/`. `src` is a build-time directory that the backend strips, so installed users type the same import either way. The layout changes where your source lives and how your own environment resolves imports, not the shape of the shipped artefact.
- When would you deliberately keep a flat layout?When nothing is published: a service deployed from its own checkout or image, a data job, a script, a teaching repository. The guarantee src layout buys is that your test run exercises the installed distribution, and if there is no distribution to drift from the tree, that guarantee has nothing to protect. The extra directory is then just ceremony.
Flat layout is demonstrating the prototype on the workbench it was built on; src layout is boxing it up first and demonstrating from the box that ships.
saying these in an interview costs you the question
- Calling src itself an importable package
- Thinking src layout changes the import name users type
- Claiming the wheel ships a top-level src directory
- Treating the choice as pure cosmetic taste
- Saying flat layout is always the modern default