Given a ASCII string containing control characters, compute what it should look like when printed to a terminal. Imagining the behaviour of a cursor, this is how to treat each character in the input:

  • 0x08 backspace (\b): go left one (if already at the start of a line, do not go up)
  • 0x09 horizontal tab (\t): go right one, and then right until the column number (0-indexed) is a multiple of 8
  • 0x0A line feed (\n): go down one line and back to the start of the line
  • 0x0B vertical tab (\v): go down one line without changing horizontal position
  • 0x0D carriage return (\r): go back to the start of the line
  • 0x20 space ( ): overwrite the previous character with a space, and go right one (This is the same behaviour as a normal character but it's here just for clarity)
  • Any other printable ASCII character should be appended literally
  • Any characters that aren't listed above (other control characters, NULL bytes, Unicode, etc.) will not be given in the input, so you don't have to handle them

Note: The behaviours above are those of modern terminal emulators; in the olden days, on a printer or teletype, \n would have done what \v does here, and \v would have moved the print head down so that the line number was a multiple of 8 (or however else the tab stops were configured). More information

Since this is like a terminal or printer, you can assume the output will never be longer than 80 columns.

Gaps that were never printed on (because the cursor moved over it) should be filled in with spaces, but gaps that are further to the right than the cursor ever went should be stripped off.

If you try these in a shell (particularly those with \b and \r), the shell prompt may overwrite some of the text - try printing a newline afterwards or add a ; sleep 1 to see the effect properly.

Here is a reference implementation: Try it online!

Test cases

Input and output are given in C-style escaped string syntax. To clarify, your program does not need to interpret backslash escape sequences - the input will contain the literal control codes themselves.

Input           Output       
""              ""
"A"             "A"
" "             " "
"\n"            "\n"
"A\nB\nC"       "A\nB\nC"
"\t"            "        "
"A\t"           "A       "
"A\t\t"         "A               "
"\tA"           "        A"
"A\tB\tC"       "A       B       C"
"ABCDEF\t"      "ABCDEF  "
"ABCDEFGH\t"    "ABCDEFGH        "
"\b"            ""
"A\b"           "A"
"A\bB"          "B"
"A\n\bB"        "A\nB"
"AB\b\bC"       "CB"
"A\b "          " "
"\r"            ""
"A\r"           "A"
"A\rB"          "B"
"A\rB\rC"       "C"
"ABC\rD"        "DBC"
"A\rB\nC"       "B\nC"
"A\n\rB"        "A\nB"
"A \r"          "A "
"A\t\r"         "A       "
"AB\vC\rD"      "AB\nD C"
"\v"            "\n"
"A\v"           "A\n "
"A\vB"          "A\n B"
"AB\vCD"        "AB\n  CD"
"AB\v\bCD"      "AB\n CD"
"AB\v\rCD"      "AB\nCD"
"AB\tC\rD"      "DB      C"
"AB\t\bC"       "AB     C"
"AB\b\t"        "AB      "
"ABCDEF\b\t"    "ABCDEF  "
"a very long string that is approaching the 80-column limit\t!\n" "a very long string that is approaching the 80-column limit      !\n"


  • \$\begingroup\$ I'm guessing echo with 0 bytes would be off-topic. :P \$\endgroup\$
    – Etheryte
    Apr 15, 2021 at 14:39
  • 1
    \$\begingroup\$ echo doesn't format the whitespace, your terminal emulator does. So you're welcome to submit xterm for however large that is (very), but echo just prints the control code bytes so it doesn't answer the question. Not to mention neither xterm nor echo are programming languages. \$\endgroup\$
    – pxeger
    Apr 15, 2021 at 15:00
  • \$\begingroup\$ @pxeger is the same caveat true of printf? \$\endgroup\$
    – Jonah
    Apr 15, 2021 at 15:52
  • 2
    \$\begingroup\$ @Jonah yes, it's the same for all ways of printing text - the terminal is what does the text layout. If you run printf '\b' | xxd you will see that all printf is doing is outputting the byte 0x08, not spaces or anything \$\endgroup\$
    – pxeger
    Apr 15, 2021 at 15:56
  • \$\begingroup\$ Thanks for the explanation, that's interesting and not something I was aware of before. As an unrelated comment, answers don't have to be in programming languages only though, a number of challenges have valid answers in pure SVG, HTML etc. \$\endgroup\$
    – Etheryte
    Apr 15, 2021 at 16:15

3 Answers 3


Charcoal, 31 bytes


Try it online! Link is to verbose version of code. Explanation:


Loop over the input string.


Switch over the ordinal of the character, since Charcoal doesn't have ASCII control characters in its code page.


For \b, move left if not already in the left column.


For \t, move right until the next multiple of 8.


For \n, move to the start of the next line.


For \v, move down a line.


For \r, move to the start of the current line.


Otherwise just overwrite the character and move right.


C (gcc), 250 246 249 248 bytes

Thanks to ceilingcat for the -4/-1, which exposed a logic error which is fixed.

Takes the original string and returns the processed string.

Annotated version:

p,q, // position, max length
c,a; // current character, buffer
  for(u=a=calloc(strlen(s),9), // allocate a buffer able to handle all tabs
    c-8? // backspace
      c-9? // tab
        c-10? // newline
          c-11? // vertical tab
            c-13? // carriage return
                *u++=c,q=++p>q?p:q // default
                  // store and advance position, updating the maximum
              :(u-=p,p=0) // carriage return
                 // reset position, not updating the maximum
            :(*u++='\n',memset(u,' ',p),u+=p) // vertical tab
              // store newline and p spaces
          :(*u++='\n',p=q=0) // newline
            // store newline and reset position and maximum
        :(i=8-p%8,i+p>q?u=memset(u+(q-p),' ',p+i-q)+(p+=i)-q,q=p:(u+=i,p+=i)) // tab
          // advance position, adding spaces to the end if needed
      :p&&u--+p--; // backspace
        // retreat position if not at start


Try it online!


JavaScript (Node.js), 140 bytes


Try it online!

Tio link use l[t]=l[t]||32 since the node version is a bit old. Changing to l[t]||=32 should work for newer version.

Input an array of integers. Output an array of array of integers.

l=[], // l is current line, output first line (may be modified later)
...s.flatMap(n=>( // for each char
p=n>31, // p: is printable?
c={     // c: position (column) of cursor; adjust cursor
8:c&&c-1, // chr(8): move left
9:c+8&-8, // chr(9): tab
11:c      // chr(11): no move
}[n]||    // based on current char
p*c,      // for other printable chars, keep cursor original position
          // for other control chars, move corsor to beginning
l[c++]=n: // printable, print
(F=t=>t?F(--t,l[t]||=32):l)(c), // control char, fill gaps with spaces
9<n&n<12?    // shall we move to next line?
[F(c,l=[])]: // output the next line
[]           // output nothing
),c=0 // cursor start with 0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.