Every few years, the tabs-vs-spaces debate resurfaces — usually triggered by a linter config, a new hire, or someone committing with the wrong editor settings. The discourse predictably collapses into aesthetics and preference. But framing it as preference misses the actual argument, and the actual argument strongly favors tabs.

Let me be specific. This is a case about indentation, the structural signal that communicates nesting depth, and for that job tabs are objectively better suited. Not because they look better, but because of what they are. Column alignment (lining up assignment operators or padding comments into a neat column) is a separate question, and my answer to it, near the end, is to skip it.

What indentation actually is

Indentation is a unit of structural depth. One level of nesting equals one unit of indent. That unit has no inherent width — width is a display concern, not a semantic one. When you write indentation with spaces, you're conflating the structural signal with an aesthetic choice, baking a visual opinion into the source file itself.

A tab character is a single byte that means indent one level. Four space characters are four bytes that mean move right 32 pixels, assuming 8px per character. One is a semantic signal. The other is a hardcoded layout instruction.

"A tab character is a single byte that means indent one level. Four space characters mean move right 32 pixels. One is semantic. The other is layout."

This distinction matters because source code is consumed in multiple contexts: editors, terminals, code review tools, diff viewers, documentation generators, screen readers. Each context has different display constraints. Tabs let each context render indentation at whatever width makes sense. Spaces don't.

The configurable width argument

The most common objection to tabs is that they "look different on everyone's machine." This is usually deployed as a bug report against tabs. It is actually their primary feature.

A developer who wants compact code can set tab width to 2. A developer who prefers generous visual separation sets it to 4. A developer with a visual impairment who finds narrow indentation hard to track might set it to 6 or 8. None of these settings change the underlying source file. The semantic structure is preserved. The display is adapted to the individual.

Spaces make this impossible. Four-space indentation is four-space indentation for every reader, in every context, on every display, forever. The original author has made a permanent aesthetic decision on behalf of every future reader of that code.

// Tab-indented: each reader sees width they prefer
function process(items) {
	for (const item of items) {
		if (item.active) {
			transform(item);
		}
	}
}

// Space-indented: one width, enforced on everyone
function process(items) {
	for (const item of items) {
		if (item.active) {
			transform(item);
		}
	}
}

Adoption across the industry

Tabs are not a fringe preference. Some of the most disciplined and widely-studied codebases in existence default to tabs — often deliberately, and with documented rationale.

Project / Standard Notes
Linux kernel Linus Torvalds, explicitly documented
Go (golang) Enforced by gofmt, non-negotiable
WordPress PHP, JS, and CSS coding standards
jQuery Core and plugins

The Go ecosystem is the most instructive case. When Google designed Go, they made tab indentation non-negotiable and enforced it via gofmt, the language's official formatter. There is no debate in Go codebases because the toolchain removes it. The Go team's reasoning aligned precisely with the semantic argument above: gofmt was about separating style enforcement from style preference.

Linus Torvalds, whose Linux kernel coding standards document is notable for its directness on this subject, simply states that indentation is "8 characters." The message is that indentation should signal nesting depth clearly and unambiguously — and deep nesting is a problem to be refactored, not hidden by narrow indentation.

The efficiency argument isn't trivial

Tabs require a single keystroke per indentation level. Spaces require between two and eight, depending on your settings. At scale, across a team, across a codebase, this is not negligible. It is also not the strongest argument for tabs — but dismissing it entirely misses what it signals.

The deeper point is that tab-indented code is easier to manipulate. Deleting one indent level on a block of code means deleting one character per line. Doing the same with four-space indentation means a find-and-replace operation or editor-specific tooling. Tabs compose more cleanly with programmatic text manipulation — which is relevant if you write scripts that process source files, generate code, or diff patches.

Addressing the counterarguments

The honest version of the spaces argument has two components: consistency and tooling. The consistency argument holds that if everyone uses spaces, the code looks the same everywhere. This is true. It is also a reason to prefer tabs, not spaces — if your team agrees on tab width in their editor configs. The only case where spaces guarantee visual consistency over tabs is when you don't trust your collaborators to configure their editors. That is an onboarding and documentation problem, not a tabs problem.

The tooling argument historically had more force. Some older tools and web-based interfaces rendered tabs poorly or inconsistently. GitHub, for a period, would expand tabs to eight characters by default, which made tab-indented code appear to have extreme indentation. This is less true now — most platforms have improved tab handling or added configuration. More practically: nobody writes production code in a browser-based text input. The relevant rendering contexts are editors, and every major editor has had configurable tab width for decades.

Skip the alignment

Column alignment is the one place spaces usually come up as the answer: lining up the values in a block of assignments, or padding comments into a tidy column. It does look nice. It's still best skipped.

Alignment is padding: extra spaces whose only job is to line things up, and someone has to maintain them. Rename one variable in an aligned block and every line around it has to be re-padded, so a one-line change becomes a diff that touches the whole block. Reviewers end up reading whitespace churn instead of the change, and formatters and editors quietly disagree about how the padding should look.

// No column alignment: one space, nothing to re-pad when a name changes
const firstName = "Ada";
const lastName = "Lovelace";
const birthYear = 1815;

// Indentation is structural: use tabs
if (condition) {
	doSomething();
	doSomethingElse();
}

Human readability still matters: people review this code, debug it and own it. But more and more code is now written and refactored by AI tools, which don't need hand-made alignment and don't reliably keep it. Visual alignment was already a weak reason to put spaces in source files, and it matters less every year. Clear structural indentation still does its job no matter who, or what, is reading.

The practical recommendation, then: use tabs for indentation, and skip alignment. Most linters and formatters can enforce both. The argument is not "tabs everywhere"; it's "tabs for the thing tabs are designed for."

A word on standards

The proliferation of competing standards — PSR-2, PEP 8, the Airbnb style guide, Google's various language guides — creates real friction in teams that work across languages and ecosystems. The instinct to resolve this friction by picking a single style for everything is understandable but probably wrong. Python's parser has historically been sensitive to mixing tabs and spaces in ways that caused genuine bugs; following PEP 8 there has a stronger rationale than just aesthetics. Go makes the decision for you. PHP's PSR standards exist to enable framework interoperability, and following them is a practical decision about ecosystem participation.

The point is not that everyone should use tabs regardless of context. It's that the default intellectual position — "spaces are the professional standard, tabs are for miscreants" — does not survive contact with the actual reasoning. The professional standard, across a significant portion of serious codebases, is tabs. The reasoning behind that standard is sound. The burden of justification should sit with the status quo, not with the alternative.

"The default position — spaces are professional, tabs are for miscreants — does not survive contact with the actual reasoning."

Configure your editor. Set your tab width. Pick the display that serves your reading preferences. That is exactly the point.


References: Linux Kernel Coding Style (kernel.org); Go FAQ — Formatting (go.dev); WordPress Coding Standards (developer.wordpress.org); PHP-FIG PSR-2 (php-fig.org); PEP 8 — Style Guide for Python Code (python.org)