Curly braces: An evolution of Unix and C

thaliaarchi1 pts0 comments

Curly braces: An evolution of UNIX and C | Thalia Archibald’s blog

Thalia Archibald's blog

Curly braces: An evolution of UNIX and C

19 May 2026

How were { } curly braces typed with a Teletype Model 33 on UNIX? These<br>characters are especially important for C, but absent on this terminal. I was<br>just asked a similar question 1 and in response, this is a tour of the<br>coevolution of UNIX and C, from this perspective, featuring “hello, world”<br>through the ages.

This work is entirely my own (no AI) and the code samples are my construction.<br>Sources for all inferences are cited.

ASCII 1963

The Teletype Model 33 famously couldn’t write lowercase letters. This<br>teleprinter was designed around the first edition of the ASCII standard, ASA<br>X3.4-1963, which hadn’t yet decided lowercase was worth adding. Some in the<br>committee thought more control characters would be a better use of the limited<br>encoding space. The standard soon evolved into its modern form, but the Model 33<br>was the first commercial use of ASCII and wildly popular, so its issues stuck.

In addition to missing lowercase, ASCII 1963 and the Model 33 lacked { }<br>curly braces, | vertical bar, ` backtick, and ~ tilde, and they had<br>↑ up arrow instead of ^ caret and ← left arrow instead of _ underscore.

Trigraphs and digraphs

Curly braces are a prominent part of C syntax, used for blocks. For example:

int main(int argc, char *argv[]) {<br>printf("hello, world!\n");

To support character sets without these characters, C89 invented trigraphs, so<br>{ could be written as ?? and } as ??>:

int main(int argc, char *argv[]) ??<br>printf("hello, world!\n");<br>??>

The trigraph ??/ for \ backslash can be used at the end of a line to produce<br>a line continuation, which was lexical undefined behavior when within a<br>universal character name. I encountered this case while writing a static<br>analysis, but it was later fixed in C++26 2.

Then, C95 introduced nicer-looking digraphs, so { can be written as and<br>} as %>:

int main(int argc, char *argv[])<br>printf("hello, world!\n");<br>%>

But, trigraphs were only introduced after the Teletype Model 33 was obsolete.<br>How did they write C code in the early ’70s?

Terminal drivers

Starting in UNIX V4 in November 1973, the teletype driver would translate<br>between \( and { and between \) and }:

main(argc, argv)<br>char *argv[];<br>\(<br>printf("hello, world!\n");<br>\)

This support was added sometime between the nsys kernel 3 in August<br>1973 and the V4 manual in November 1973 4. The Utah_v4 kernel (June 1974)<br>5 and Dennis_v5 kernel (November 1974) 6 have support, but<br>nsys, a pre-release version of V4 before pipes were added back in, does not. The<br>V2 and V3 kernels, which were written in assembly, did not survive, but the nsys<br>kernel matches the V3 manual 7 and the V1 kernel 8.

UNIX exposes devices through a common byte stream interface and this character<br>translation is transparent to user space programs. Programs use the bytes for<br>ASCII { } and the kernel translates them to \( \) on write to a<br>Teletype Model 33, or in reverse on read.

This escaping evolved out of the need to delete characters sent by a terminal,<br>since teleprinters can’t erase text that’s already been printed on paper. The<br>scheme they used, inherited from Multics, was to process input by lines and<br>interpret # “erase” as deleting the previous character and @ “kill” as<br>clearing the current line. Either character can be escaped with backslash to get<br>the literal character.

For example, this Utah_v4 session writes that program with a Teletype Model 33<br>and uses @ and # to fix a few mistakes:

% ed hello.c<br>main(argc, argv)<br>char *argv[];<br>\(<br>printf("hallo, welt@ printf("hello #, world!\n");<br>\)<br>63<br>% cc hello.c<br>% a.out<br>hello, world!

If you signed in with another terminal, you would see:

% cat hello.c<br>main(argc, argv)<br>char *argv[];<br>printf("hello, world!\n");

What about before UNIX V4? From its start in June 1972 9, C used only<br>braces. You just needed to use a terminal that could produce braces.

Early C structs

Interestingly, when structs were first added to C in December 1972<br>10, they used parentheses instead of braces! For a time around<br>nsys in August 1973, you could even write structs with either parentheses or<br>braces. It was fully switched to only the modern syntax at the latest by June<br>1974 11. This definition in nsys uses both styles 12:

struct user {<br>int u_rsav[2]; /* must be first */<br>/* ... */<br>struct (<br>int u_ino;<br>char u_name[DIRSIZ];<br>) u_dent;<br>/* ... */<br>} u; /* u = 140000 */

But that’s just one language feature; blocks still required braces.

Before C was B, an interpreted language made by Ken Thompson for UNIX. B had no<br>types—every value was a machine word—, perfect for the PDP-7 that UNIX started<br>on, with 18-bit words.

A descendant of this early B remained in use for the Honeywell 6070, far after<br>UNIX B was replaced with C. This machine has 36-bit words, so four characters<br>fit into a word. The 1973 B language tutorial for the H6070 13 had the<br>first-ever “hello, world” program, also...

hello braces unix world argv model

Related Articles