Indent with tabs, align with spaces


When I was much younger, I used to use tabs in my code. After some time, I started getting headaches because the code gets messed up if some editor/viewer uses different tab size. For example, this is the code I wrote assuming tab to be 3 characters:

void my_func(void)
   int var1          = 1;
   int another_var   = 2;
   int third_var     = 3;
   if (  something
      && something_else
      && maybe_something_else
      call_function( long_param[ long_subscript ],
                     another_param );

And here is what it looks like if someone reads it with tab equal to 8 characters:

void my_func(void)
        int var1                                = 1;
        int another_var = 2;
        int third_var           = 3;
        if (  something
                && something_else
                && maybe_something_else
                call_function( long_param[ long_subscript ],
                                                        another_param );

The same happens if I need to edit some foreign code with tabs, and tab size is assumed to be other than my settings. So, after all, I considered tabs as unconditional evil, banned them from my own code and blamed everybody else who uses tabs. My colleagues (which were more experienced than me) agreed, so I was quite sure that I'm “doing that right”.

However, recently I figured out that tabs are actually misused by a lot of people, including myself. If we use tabs correctly, they are great tool!

Indentation and alignment

The main problem is that a way too many people don't understand (or don't care about) the difference between an indentation and an alignment.

This is an indentation:

void my_func(void)
   if (everything_ok()){

And this is an alignment:

int var1         = 0,
    another_var  = 1,
    third_var    = 2;

It's very important to only use spaces for alignment. Never, ever, use tabs for alignment. Tabs may be used for indentation only.

Let's combine previous two examples (for clarification, I indicate tabs as >--, and spaces as . (a dot)).

Tab is 3 characters:

void my_func(void)
>--if (everything_ok()){
>-->--int var1.........= 0,
>-->--....another_var..= 1,
>-->--....third_var....= 2;

Tab is 4 characters:

void my_func(void)
>---if (everything_ok()){
>--->---int var1.........= 0,
>--->---....another_var..= 1,
>--->---....third_var....= 2;

Tab is 8 characters:

void my_func(void)
>-------if (everything_ok()){
>------->-------int var1.........= 0,
>------->-------....another_var..= 1,
>------->-------....third_var....= 2;

It looks fine whatever the tab size is. And that's great. Say, if I'm on the netbook with small screen, I'd probably set tab to 2 characters in order to see more code at my screen, and it will look fine. I wouldn't have this kind of convenience if code would have beed indented with spaces.

Anybody who reads the code could use his or her favorite preferences on tab size, and nothing will be mixed up. That makes tabs actually a superior to spaces, when it comes to indentation. Try it!

Vim settings

Although the indentation / alignment politics are independent of the editor being used, I decided to put some recommendations on how to set up Vim correctly so that it would be easy for you to follow the principle “Indent with tabs, align with spaces” in this editor, if you use it.

Unfortunately, at the moment Vim can't handle this automatically. It's too bad actually. Anyway, there are a couple of tips to make Vim behave better.

See your tabs and trailing spaces

In order to write code that is correctly indented and aligned, it is almost inevitable to see your tabs and spaces. To this end, put this to your vimrc:

" display tabs and trailing spaces
set list listchars=tab:>-,trail:.,extends:>,precedes:<

and you will see >-- for tabs and . for trailing spaces. I've set up my colorscheme so that it doesn't distract me from actual code.

Smart Tabs

There is a plugin “Smart Tabs”. When you hit <Tab> key at the beginning of the line, then tab character is inserted; otherwise (if there is something other than a whitespace before the cursor), the proper amount of spaces are inserted.


I found it useful to turn on the option copyindent:

" copy indent from previous line: useful when using tabs for indentation
" and spaces for alignment
set copyindent

Then, vim will copy indentation from previous line. Consider (the | sign represents cursor position):

>-->--int var1 = 1,
>-->--....var2 = 2,|

When you press Enter, you most likely want the editor to keep amount of tabs/spaces, so that it becomes:

>-->--int var1 = 1,
>-->--....var2 = 2,

This is what copyindent option is for.


Tabs are a decent tool when they are used properly, and they are a call for trouble if used carelessly. I consider this scale correct:

  • Use tabs for indentation, spaces for alignment: the superior approach. Code is indented and aligned properly, and indentation is flexible.
  • Use spaces for both indentation and alignment: still fine, but not flexible enough.
  • Use tabs for both indentation and alignment: completely unacceptable, leads to headaches.

Personally, in most of my projects I still use spaces only. That's mostly because we often want the editor to automatically align the code for us, but editors, unfortunately, aren't smart enough to always distinguish correctly where do we need an alignment and where do we need an indentation. I hope I'll find the way to teach my editor some day, but now at least I know how to do things right when I need to work on foreign projects that use tabs.

If you use spaces in your code for indentation, I hope that after reading this article you will at least think of giving tabs a chance. And if you already use tabs, I really hope you use them right!


real name, 2015/09/21 20:14

On some lines, you've got a ';' where I think you should have a ','. :P

Dmitry Frank, 2015/09/21 20:24

Oh, true! You know, untested copy-pasted code. :) Thanks! Fixed.

isomorphismes, 2017/06/24 14:03

:set tabstop, :set shiftwidth — spend 5 seconds on foreign tab usage and everyone can use tabs however they want. I like them as column separators (.tsv), for example.

If you use \t you can achieve all kinds of great things, like:

f( g( h(x) ) )

not getting lost in parentheses *or* taking up multiple lines for something simple.

(Although really we should move beyond paren-matching. What a waste of time for humans!)

IDE's are here to help us. It is their job to make people's text readable. The user is right. If the user wanted to use long lines, UTF-8 characters, tabs however s/he pleases, etc —- it's very possible, and reasonable to ask, for the IDE to wrap / read / align that text for the next user.

Enter your comment (please, English only). Wiki syntax is allowed:
   ___    __ __   ___   ____   _____
  / _ )  / // /  / _ \ / __ \ / ___/
 / _  | / _  /  / // // /_/ // /__  
/____/ /_//_/  /____/ \____/ \___/
articles/indent_with_tabs_align_with_spaces.txt · Last modified: 2015/09/21 20:23 by dfrank
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0