Danny's Blog
See all posts


Written by: Danny Spencer

Updated November 10, 2022


Update (2022-11-10): I've added this section and the following "tabs" section to better contextualize my point of view

i.e. Why I personally prefer spaces over tabs

I'm aware that the "tabs vs spaces" debate has some very passionate participants - plenty of them can be found on Reddit and Twitter. Among many things, it certainly wins the bikeshed award when it comes to code style preferences.

With that said, I'm not a hardcore cruisader for spaces, though I admittedly get frustrated when the topic gets reduced to "who in their right mind would...".

In this article I intend to explore an often-overlooked reason why spaces are preferred by advocates, but primarily myself.

Why do people like tabs?

My understanding is that the usual arguments are the following:

Okay, a few of those are arguments are from the Tabs vs Spaces segment from Silicon Valley.

The strongest argument in my opinion, is the editor preferences one. However, I think it grossly overstates how important adjusting indentation width when viewing code really is. Especially when it competes with other readability concerns (which I explore later in favor of spaces).

The "press the tab key once" argument is silly (at least nowadays), because modern code editors will create spaces for you whenever you press the tab key.

Spaces support Lisp-style indentation

I write a lot of Clojure, and virtually no Clojure programmer uses tabs. This is because the prevalent Lisp styles line up forms.

Lisp code is typically indented to line up with previous forms.

;;; Nested list of numbers
((1 2)
 (3 ((4 5 6)
     (7 8)))

  ((= type :buy)  (buy-item  item-name
  ((= type :sell) (sell-item item-name

As far as contemporary Lisp style goes, indentation is variable-width and doesn't deal very well with multiples-of-fixed-width indentation. You may indent with 1, 4, 6, or even 7 spaces before the current line's form.

Spaces allow developers to align code

Here's some C++ code that aligns a function signature on multiple lines. This style is quite common:

void doSomething(int foo,
                 std::vector<std::string> bar) {

I've also seen SQL queries that right-align clause keywords. This is my personal preference for writing large, presentable SQL statements:

SELECT department, COUNT(*) as count
  FROM my_table
 WHERE salary < 60000
 GROUP BY department;

If you used tabs as the primary whitespace AND wanted to vertically align code, you'd have to ensure that (a) the tab width is the same for everyone viewing your code, and (b) you won't need to add extra spaces to line up your code just right.

Tabs typically have a width of 2 characters or more (though usually 4 or 8). That means that if you use tabs to indent the following code, you'd be off-by-one:

void doSomething(int foo,
                std::vector<std::string> bar) {

What do you do in this case? If you really wanted to stick with vertical indentation, you'd be forced to add a space. Now you have a tab+spaces hybrid! Gross!

Not to mention that many text editors have configurable (or a hard-coded-yet-different) tab width, so it'll look like a total mess on someone else's machine.

void doSomething(int foo,
                                std::vector<std::string> bar) {

Whitespace in general is a great idea. Let's do more of it!

Code is for humans, right? Whenever whitespace helps with readability, we should do it.

function doIt() {
  /*** Vertical alignment for assignments ***/
  const foo             = "bar"
  const favorite_color  = "red"
  const number_of_items = 5

  /*** New section (newlines are whitespace, too!) ***/
  newSection({ foo, favorite_color, number_of_items })

  /*** Final section ***/
  return `foo: ${foo}`

I create sectioned comments quite a bit. This is useful for sections of code that have a natural linear progression.

Many developers will instead opt for decomposing large units into multiple units (functions, files, classes, etc...), but this is merely one form of organization with its own tradeoffs. If the only issue with a large unit is finding relevant sections, then whitespace with comments could serve you quite well.

If however, you want to unit-test these segments, then decomposition might work better for you. Or perhaps the large function can be tested with mocks instead. Whatever works best. :)

Other links