Code of the Day
IntermediateStructs and enums

enum and bit flags

Represent named constants with enum and combine options efficiently with bitwise OR — the bit flags pattern used throughout C APIs and system headers.

CIntermediate10 min read
Recommended first
By the end of this lesson you will be able to:
  • Declare an enum and use its values as named constants
  • Explain that enum values are integers under the hood
  • Define a set of bit flags using powers of two
  • Use bitwise OR to combine flags and bitwise AND to test them

An enum (enumeration) is a way to define a set of named integer constants. Instead of writing if (direction == 2) and wondering what 2 means, you write if (direction == SOUTH) and the code documents itself. Enums are how C APIs represent options, states, error codes, and directions.

Basic enum

#include <stdio.h>

enum Direction {
    NORTH,  /* 0 */
    SOUTH,  /* 1 */
    EAST,   /* 2 */
    WEST    /* 3 */
};

const char *direction_name(enum Direction d) {
    switch (d) {
        case NORTH: return "North";
        case SOUTH: return "South";
        case EAST:  return "East";
        case WEST:  return "West";
        default:    return "Unknown";
    }
}

int main(void) {
    enum Direction heading = NORTH;
    printf("Heading: %s\n", direction_name(heading));
    return 0;
}

Enum values start at 0 by default and increment by 1. You can override them:

enum HttpStatus {
    HTTP_OK       = 200,
    HTTP_NOT_FOUND = 404,
    HTTP_SERVER_ERROR = 500
};

Any value not explicitly assigned follows the previous value + 1.

typedef enum

The same typedef pattern applies:

typedef enum {
    STOPPED,
    RUNNING,
    PAUSED,
    ERROR
} ProcessState;

ProcessState state = RUNNING;

Now ProcessState can be used without the enum keyword.

Enums under the hood

Enum values are plain integers. This means you can accidentally compare or assign the wrong enum type and the compiler may not warn you:

enum Color { RED, GREEN, BLUE };
enum Size  { SMALL, MEDIUM, LARGE };

enum Color c = SMALL; /* compiles without error -- SMALL is 0, same as RED */

C's enum is weaker than Rust's or Java's. Treat enum values as typed constants for documentation purposes, but know they are integers.

Bit flags

When you need to represent multiple independent options that can be combined (e.g., file permissions: readable AND writable AND executable), bit flags are the pattern.

Define each flag as a power of two — each occupying a single bit:

typedef unsigned int FileMode;

#define MODE_NONE    0
#define MODE_READ    (1 << 0)  /* 1  = 0001 */
#define MODE_WRITE   (1 << 1)  /* 2  = 0010 */
#define MODE_EXECUTE (1 << 2)  /* 4  = 0100 */

Or equivalently with an enum:

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

Combining flags with bitwise OR

unsigned int mode = MODE_READ | MODE_WRITE; /* 1 | 2 = 3 = 0011 */

sets each bit independently. MODE_READ | MODE_WRITE gives a value where both bit 0 and bit 1 are set.

Testing flags with bitwise AND

if (mode & MODE_READ) {
    printf("Readable\n");
}
if (mode & MODE_WRITE) {
    printf("Writable\n");
}
if (!(mode & MODE_EXECUTE)) {
    printf("Not executable\n");
}

mode & MODE_READ is non-zero (truthy) if the READ bit is set, zero (falsy) if not.

Setting, clearing, and toggling bits

unsigned int flags = 0;

flags |= MODE_READ;     /* set the READ bit */
flags &= ~MODE_WRITE;   /* clear the WRITE bit */
flags ^= MODE_EXECUTE;  /* toggle the EXECUTE bit */

~MODE_WRITE is the bitwise NOT of MODE_WRITE: all bits set except bit 1. AND with this clears bit 1.

A practical example: open() flags on Linux

The POSIX open() system call uses this exact pattern:

#include <fcntl.h>

int fd = open("file.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);

O_RDWR, O_CREAT, and O_TRUNC are bit flags defined in <fcntl.h>. You combine them with |. The kernel tests individual bits to determine what to do.

Bit flags compact multiple booleans into one integer. Passing eight separate int parameters to indicate options is unwieldy. Passing one unsigned int with eight bits is concise and extensible. This is why bit flags appear throughout POSIX, network protocols, and graphics APIs.

Where to go next

Next: the linked list lab — you will build a singly linked list in C using structs and pointers, supporting insert, delete, and traversal.

Finished reading? Mark it complete to track your progress.

On this page