Skip to content

typedef in C

Updated 8 min read Aimerscrew

By now you've typed struct Account and enum Light many times. typedef in C lets you give any type a shorter, clearer name, so you can write Account or Light instead. It's used everywhere in real C code, from your own structs to standard types like size_t and int32_t. In this lesson, you'll learn how to create type aliases, use typedef struct, tame function pointer syntax, and spot when typedef is making code worse instead of better.

🌍 Real-life example

Your friend Alexander goes by "Alex". When someone says "Alex is here", you know exactly who they mean: it's the same person, just a shorter name. A typedef is a nickname for a type. The compiler treats the nickname and the full name as the very same type.

What is typedef in C?

typedef is a keyword that creates an alias (another name) for an existing type. The syntax looks like a variable declaration with typedef in front, where the "variable name" becomes the new type name:

typedef unsigned int Count;   /* Count now means unsigned int */
typedef double Celsius;       /* Celsius now means double     */

Count visitors = 120;
Celsius room = 21.5;

Here, Count and unsigned int are interchangeable. typedef doesn't create a new type with new rules; it just adds a name.

A simple typedef example

A good alias tells the reader what a value means, not just how it's stored. Compare double to_celsius(double f) with this:

#include <stdio.h>

typedef double Celsius;
typedef double Fahrenheit;

Celsius to_celsius(Fahrenheit f) {
    return (f - 32.0) * 5.0 / 9.0;
}

int main(void) {
    Fahrenheit outside = 68.0;
    Celsius today = to_celsius(outside);

    printf("%.1f F is %.1f C\n", outside, today);
    return 0;
}
68.0 F is 20.0 C

The function's first line now documents itself: it takes Fahrenheit and returns Celsius. Keep in mind the compiler still sees two plain double types, so it won't stop you from mixing them up.

typedef struct: cleaner struct code

The most popular use of typedef is with structs. Instead of writing struct Song everywhere, you define the struct and its alias in one go:

#include <stdio.h>

typedef struct {
    char title[40];
    char artist[30];
    int seconds;
} Song;

void print_song(const Song *s) {
    printf("%s - %s (%d:%02d)\n", s->title, s->artist, s->seconds / 60, s->seconds % 60);
}

int main(void) {
    Song playlist[3] = {
        {"Morning Run", "The Pacers", 215},
        {"City Lights", "Nova Bay", 187},
        {"Slow Tide", "Harbor Kids", 242}
    };
    int total = 0;

    for (int i = 0; i < 3; i++) {
        print_song(&playlist[i]);
        total += playlist[i].seconds;
    }
    printf("Playlist length: %d:%02d\n", total / 60, total % 60);
    return 0;
}
Morning Run - The Pacers (3:35)
City Lights - Nova Bay (3:07)
Slow Tide - Harbor Kids (4:02)
Playlist length: 10:44

Notice that Song works for both the array and the function parameter, and the word struct never appears outside the definition. The %02d pads the seconds with a leading zero, so 7 seconds prints as 07.

When the struct needs a tag too

The struct above has no tag (it's anonymous), and that's fine most of the time. But a struct that contains a pointer to its own type needs a tag, because the alias doesn't exist yet inside the braces:

typedef struct Node {
    int value;
    struct Node *next;   /* must use the tag here */
} Node;                  /* from now on, just Node */

Using the same word for the tag and the alias is common and perfectly legal. You'll use exactly this shape in the linked list lesson.

Three typedef aliases with arrows to the full types they stand for: Song means struct with title, artist and seconds; Operation means double (*)(double, double); int32_t means int on most systems
A typedef name is a nickname. The compiler swaps in the full type behind it.

typedef for function pointers

Function pointer syntax is famously hard to read. A typedef gives the whole type a single name. Compare:

double (*ops[2])(double, double);   /* without typedef */

typedef double (*Operation)(double, double);
Operation ops[2];                   /* with typedef */

Both lines declare an array of two pointers to functions that take two doubles and return a double. The second is far easier to read. Here it is in a full program:

#include <stdio.h>

typedef double (*Operation)(double, double);

double add(double a, double b) {
    return a + b;
}

double multiply(double a, double b) {
    return a * b;
}

double apply(Operation op, double x, double y) {
    return op(x, y);
}

int main(void) {
    Operation ops[2] = {add, multiply};
    const char *names[2] = {"add", "multiply"};

    for (int i = 0; i < 2; i++) {
        printf("%s(6, 4) = %.1f\n", names[i], apply(ops[i], 6.0, 4.0));
    }
    return 0;
}
add(6, 4) = 10.0
multiply(6, 4) = 24.0

Fixed-width types: int32_t and uint8_t are typedefs

The size of int can differ between systems. When you need an exact size, for example for a file format or a network message, the header <stdint.h> provides fixed-width integer types. They are simply typedefs that each system sets up correctly. On a typical 64-bit Linux system, the header contains lines similar to typedef int int32_t; and typedef unsigned char uint8_t;.

TypeMeaningRange
uint8_tUnsigned, exactly 8 bits0 to 255
int32_tSigned, exactly 32 bitsabout -2.1 billion to 2.1 billion
int64_tSigned, exactly 64 bitsabout ±9.2 quintillion
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
    uint8_t brightness = 250;      /* a smart bulb: 0 to 255 */
    int32_t balance = -1500;
    int64_t views = 9000000000;

    printf("Sizes: %zu, %zu, %zu bytes\n", sizeof(uint8_t), sizeof(int32_t), sizeof(int64_t));
    printf("brightness = %d\n", brightness);
    printf("balance = %" PRId32 "\n", balance);
    printf("views = %" PRId64 "\n", views);

    brightness = brightness + 10;  /* 260 doesn't fit in 8 bits */
    printf("brightness after +10 = %d\n", brightness);
    return 0;
}
Sizes: 1, 4, 8 bytes
brightness = 250
balance = -1500
views = 9000000000
brightness after +10 = 4

A uint8_t can't hold 260, so the value wraps around: 260 minus 256 is 4. The PRId32 and PRId64 macros from <inttypes.h> expand to the right printf format for each type on every system. You already met another standard typedef: size_t, the type of sizeof, from the sizeof lesson.

typedef vs #define

You might think #define could do the same job. It can't, because #define is plain text replacement, while typedef is understood by the compiler. Pointers show the difference:

#define CHAR_PTR char *
typedef char *String;

CHAR_PTR a, b;   /* becomes: char * a, b;  so b is just a char! */
String c, d;     /* c and d are both char * */

When not to use typedef

typedef is a tool for clarity. Used carelessly, it hides information instead:

  • Pointless aliases like typedef int Integer; add a name without adding meaning.
  • Hiding pointers, as in typedef Song *SongPtr;, makes it easy to forget you're holding a pointer that must not be NULL or must be freed. Many teams prefer writing the * explicitly.
  • Hiding a struct can surprise readers who don't realize a "simple" value is a large record that gets copied on every function call.

💡 Tip

Good uses: struct and enum names, function pointer types, and names that carry meaning (Celsius, Dollars). If an alias doesn't make the code easier to understand, leave it out.

Common mistakes with typedef

1. Writing struct before an anonymous typedef name

With typedef struct { ... } Song;, there is no struct Song. Writing struct Song s; creates a different, incomplete type, and using its members fails with errors such as "storage size of 's' isn't known". Just write Song s;.

2. Putting the new name in the wrong place

The alias goes where a variable name would go: typedef double Celsius;, not typedef Celsius double;. For function pointers, the name sits inside the parentheses: typedef double (*Operation)(double, double);.

3. Using the alias inside its own struct

Inside the braces of typedef struct Node { ... } Node;, the alias Node doesn't exist yet. Write struct Node *next; there.

4. Expecting typedef to add type safety

Celsius and Fahrenheit are both double, so passing one where the other is expected compiles without a warning. If you need the compiler to tell them apart, wrap each in its own struct.

Practice exercise

🏋️ Try it yourself

Create a typedef struct named City with a name and a temperature. Fill an array with Oslo (4.5), Cairo (29.0), Lima (18.2) and Tokyo (15.8). Write a function const City *warmest(const City *cities, int count) that returns a pointer to the warmest city, then print Warmest: Cairo (29.0 C).

Show solution
#include <stdio.h>

typedef struct {
    char name[20];
    double temp;
} City;

const City *warmest(const City *cities, int count) {
    const City *best = &cities[0];
    for (int i = 1; i < count; i++) {
        if (cities[i].temp > best->temp) {
            best = &cities[i];
        }
    }
    return best;
}

int main(void) {
    City cities[4] = {
        {"Oslo", 4.5},
        {"Cairo", 29.0},
        {"Lima", 18.2},
        {"Tokyo", 15.8}
    };

    const City *hot = warmest(cities, 4);
    printf("Warmest: %s (%.1f C)\n", hot->name, hot->temp);
    return 0;
}
Warmest: Cairo (29.0 C)

What's next?

You can now model real-world data with structs, unions, enums and typedefs. But everything disappears when your program ends. Next, start the File Handling chapter with file handling in C and learn to save data to disk and load it back.

Key takeaways

  • typedef creates an alias, a new name for an existing type; it does not create a new type.
  • typedef struct { ... } Name; lets you write Name instead of struct Tag.
  • A self-referencing struct needs a tag: typedef struct Node { struct Node *next; } Node;.
  • typedef makes function pointer types readable, for example typedef double (*Operation)(double, double);.
  • Fixed-width types like int32_t and uint8_t from stdint.h are typedefs set up for each system.
  • Unlike #define, typedef is understood by the compiler, so typedef char *String; works correctly for several variables.
  • Avoid pointless aliases and aliases that hide pointers.

Frequently asked questions

What is typedef in C?

typedef is a keyword that gives an existing type a new name. For example, typedef unsigned int Count; lets you write Count instead of unsigned int. Both names mean the same type.

Why use typedef with struct in C?

Without typedef, you must write struct Name every time you use the type. With typedef struct { ... } Name;, you can simply write Name, which makes code shorter and easier to read.

What is the difference between typedef and #define in C?

typedef is handled by the compiler and creates a real type name. #define is text replacement done before compiling, so #define PTR char * followed by PTR a, b; makes only a a pointer, while a typedef would make both pointers.

Is int32_t a typedef?

Yes. int32_t, uint8_t and the other fixed-width types in stdint.h are typedefs that each system maps to a built-in type of the right size, such as int for int32_t on most systems.

Does typedef create a new type in C?

No. It only creates an alias. The compiler treats the alias and the original type as identical, so it will not warn if you mix two aliases of the same underlying type.