Enums in C are named integer constants with cleaner syntax than #define. They look simple, but the details — underlying type, size, switch warnings, and flag patterns — are worth knowing explicitly. Most programmers leave something on the table.

Basic Syntax

#include <stdio.h>

typedef enum {
    MON = 0,
    TUE,
    WED,
    THU,
    FRI,
    SAT,
    SUN
} Weekday;

int main() {
    Weekday today = WED;
    printf("Day number: %dn", today);   /* 2 */

    if (today == WED) {
        printf("Midweekn");
    }

    return 0;
}

If you omit a value, each constant is one more than the previous. MON = 0, TUE = 1, and so on. You can mix explicit and implicit values:

typedef enum {
    HTTP_OK       = 200,
    HTTP_CREATED  = 201,
    HTTP_NOT_FOUND = 404,
    HTTP_ERROR    = 500
} HttpStatus;

The Underlying Type — Not Always int

#include <stdio.h>

typedef enum { A, B, C } Small;
typedef enum { X = 0, Y = 0x7FFFFFFF } Large;

int main() {
    printf("sizeof(Small) = %zun", sizeof(Small));  /* usually 4 */
    printf("sizeof(Large) = %zun", sizeof(Large));  /* 4 on most platforms */
    return 0;
}

The C standard says the underlying type of an enum is an implementation-defined integer type large enough to hold all the values — typically int. It is NOT guaranteed to be int. On embedded systems or with specific compiler flags, it may be smaller. Never assume sizeof(MyEnum) == sizeof(int).

Switch Completeness — The Best Enum Feature

#include <stdio.h>

typedef enum { RED, GREEN, BLUE } Color;

const char *color_name(Color c) {
    switch (c) {
        case RED:   return "Red";
        case GREEN: return "Green";
        /* BLUE missing — compiler warns with -Wswitch */
    }
    return "Unknown";   /* suppresses -Wreturn-type, but defeats the purpose */
}
gcc -Wall -Wswitch -o prog prog.c
/* warning: enumeration value 'BLUE' not handled in switch */

Compile with -Wswitch (included in -Wall). When you add a new value to an enum, the compiler will warn you about every switch that doesn’t handle it. This is the killer feature of enums over #define for sets of states. Do not add a default case if you want this warning — default suppresses it.

Enum as Bit Flags

#include <stdio.h>

typedef enum {
    PERM_NONE    = 0,
    PERM_READ    = 1 << 0,   /* 1 */
    PERM_WRITE   = 1 << 1,   /* 2 */
    PERM_EXECUTE = 1 << 2,   /* 4 */
    PERM_ALL     = PERM_READ | PERM_WRITE | PERM_EXECUTE
} Permission;

int main() {
    Permission user = PERM_READ | PERM_WRITE;

    if (user & PERM_READ) printf("can readn");
    if (user & PERM_WRITE) printf("can writen");
    if (!(user & PERM_EXECUTE)) printf("cannot executen");

    /* Add a permission */
    user |= PERM_EXECUTE;

    /* Remove a permission */
    user &= ~PERM_WRITE;

    return 0;
}

Enums make bit flag code more readable than raw integer constants. The 1 << N pattern ensures each flag occupies exactly one bit. Just be aware that combining flags (ORing them) produces a value that is not a named enum constant — the variable’s type should be an integer, not the enum type, to avoid warnings.

Pitfall: Assigning Invalid Values

typedef enum { A = 1, B = 2, C = 3 } State;

State s = 42;   /* compiles — no runtime check in C */

switch (s) {
    case A: /* ... */ break;
    case B: /* ... */ break;
    case C: /* ... */ break;
    /* 42 falls through to default or off the end */
}

C enums have no runtime validation. You can assign any integer to an enum variable. Unhandled values in switch fall through to default or off the end entirely. If the enum represents states that come from external input (network packets, files, user input), validate the value before assigning to an enum type.

Anonymous Enums for Constants

/* Use anonymous enum for compile-time integer constants */
enum { MAX_CONNECTIONS = 128 };
enum { TIMEOUT_MS = 5000 };

int connections[MAX_CONNECTIONS];  /* works as array size */

Anonymous enums give you compile-time integer constants without the type overhead of typedef enum. Unlike const int, they work as array sizes and case labels in all C standards. This pairs well with struct patterns in C for defining configuration constants alongside type definitions. Test enum behavior in our c program compiler — especially switch completeness with -Wall.

TL;DR

  • Enum values start at 0 by default and increment by 1; you can set any value explicitly
  • The underlying type is implementation-defined — don’t assume sizeof(enum) == 4
  • Compile with -Wswitch to get warned when a switch doesn’t handle all enum values
  • Don’t add a default case when you want switch completeness warnings
  • Use 1 << N values for bit flags, but store combined flags in an integer, not the enum type
  • C has no runtime validation of enum values — validate external input before assigning
  • Use anonymous enum { CONST = value } for compile-time integer constants