Tabs vs spaces
Tabs vs spaces for code indentation: accessibility, diffs, and why the debate mostly outlived its technical stakes.
Last updated
The short answer
Spaces render identically everywhere, which is why most modern style guides and formatters (Prettier, Black, PEP 8) default to them — consistency across editors and diffs matters more than the minor storage or accessibility wins tabs offer. If your language's own tooling mandates tabs (Go's gofmt, Makefiles), match that instead of relitigating the debate.
| Dimension | Tabs | Spaces |
|---|---|---|
| Rendering | Varies by editor's tab-width setting | Consistent — 1 space is always 1 space |
| File size | Smaller (1 byte per indent level) | Larger (N bytes per indent level) |
| Accessibility | Width adjustable per user or editor | Fixed width for everyone |
| Alignment | Breaks across differing tab widths | Reliable in any viewer or diff tool |
| Ecosystem default | Go (gofmt); Makefiles require it | Prettier, Black, most style guides |
Choose Tabs when
- →Your language's own tooling mandates it — Makefiles require literal tabs, and Go's gofmt rewrites everything to tabs regardless of preference.
- →Team members have different visual indent-width preferences and you want each editor to honor its own setting.
- →You care about minimizing file size at scale across a very large codebase — a tiny effect, but a real one.
Choose Spaces when
- →You want code to look identical in every editor, terminal, diff tool and code review UI without configuration.
- →You're using a formatter (Prettier, Black, most JS/Python/Ruby style guides) that defaults to spaces — fighting the default costs more than accepting it.
- →Multi-line alignment, like matching parentheses across lines, needs to survive being viewed at any tab width.
The catch nobody mentions
The debate is mostly settled by tooling now, not preference: once a project adopts an auto-formatter, tabs-vs-spaces stops being a per-commit argument and becomes a one-line config setting nobody touches again. The place it still bites is inconsistency — mixing tabs and spaces in one file, which some languages (Python) treat as a syntax error and others render as an invisible mess.
Questions
Is there an argument beyond preference?
Yes, and it is the accessibility one. A tab is a single character the reader can render at whatever width suits them, which matters to developers using large text or a braille display. Spaces impose the author's choice on everyone.
Why do most projects use spaces anyway?
Alignment renders identically everywhere, most language style guides specify them, and formatters default to them. It is a convention that won on consistency rather than on merit.
What does Python require?
Consistency. Python 3 refuses to mix tabs and spaces for indentation within a block and raises TabError, which removed the argument by making the mixed case an outright error rather than a subtle bug.
Does Go settle it?
For Go, entirely — gofmt uses tabs and is not configurable, so the question never arises. That is arguably the real lesson: the decision matters far less than having it made once and enforced automatically.
How do I stop this being an argument on my team?
Commit an .editorconfig and a formatter configuration, and run the formatter in CI. Once the tooling decides, the discussion is about the config file rather than about anyone's diff.
What about alignment within a line?
This is where the hybrid position lives: tabs for indentation, spaces for alignment, so aligned columns survive a change of tab width. It is principled, and few tools implement it well enough for it to be worth the friction.