Enums and Typedef in C: Syntax, Worked Examples, and Common Errors
Learn how C enums name integer states and typedef aliases existing types. Trace two complete programs, then avoid collisions, hidden pointers, invalid values, and size assumptions.
KnowledgeGate Team
Exam prep & CS education

enum gives readable names to integer-valued states. typedef gives an existing type a shorter or clearer spelling. The combined typedef enum form can make them look like one feature, which is where many learners get confused. Implicit sequences resume after explicit enum values, typedef aliases preserve their underlying type, and enum-to-text conversion requires an explicit lookup; the Coding & DSA courses sequence offers broader practice.
Related reading: Enum values in C and C data types.
Enum and typedef solve different naming problems
An enumeration is a distinct enumeration type whose named constants represent integer values. For example, enum AccessLevel { ACCESS_GUEST, ACCESS_EDITOR, ACCESS_ADMIN }; defines the values ACCESS_GUEST = 0, ACCESS_EDITOR = 1, and ACCESS_ADMIN = 2. A variable can then be declared as enum AccessLevel level = ACCESS_EDITOR;.
A typedef name is an alias for an existing type. After typedef unsigned long StudentId;, the declaration StudentId id = 120045UL; is simply using a clearer spelling for unsigned long. It does not create a separately checked type.
The syntactic contrast is important in C: enum AccessLevel level; needs the enum keyword. If you later define a typedef name, AccessLevel level; becomes valid. Unlike C++, C does not automatically make an enum tag into a typedef name.
First runnable example: implicit and explicit enum values
This program mixes implicit and explicit enumerator values, then maps each valid state to readable text:
#include <stdio.h>
enum TicketState {
TICKET_NEW = 0,
TICKET_ASSIGNED = 5,
TICKET_IN_PROGRESS,
TICKET_CLOSED = 10
};
const char *ticket_state_name(enum TicketState state) {
switch (state) {
case TICKET_NEW: return "new";
case TICKET_ASSIGNED: return "assigned";
case TICKET_IN_PROGRESS: return "in progress";
case TICKET_CLOSED: return "closed";
default: return "unknown";
}
}
int main(void) {
enum TicketState current = TICKET_IN_PROGRESS;
printf("values: %d %d %d %d\n",
TICKET_NEW, TICKET_ASSIGNED,
TICKET_IN_PROGRESS, TICKET_CLOSED);
printf("current: %s (%d)\n",
ticket_state_name(current), (int)current);
return 0;
}Compile it with cc -std=c17 -Wall -Wextra -pedantic enum_demo.c -o enum_demo. The output is:
values: 0 5 6 10
current: in progress (6)The first value is explicitly 0. TICKET_ASSIGNED resets the sequence to 5, so the following unspecified TICKET_IN_PROGRESS becomes 6. TICKET_CLOSED is explicitly reset to 10. The switch performs the code-to-text lookup because C does not automatically turn an enumerator name into a string.

Choose enum values deliberately, and never confuse names with strings
Use one of three stable patterns. Omit all initialisers for simple zero-based states. Assign every public or stored code explicitly. Or assign one value and allow only the immediately following private values to increment. For visibly stable numeric codes, write enum Priority { PRIORITY_LOW = 10, PRIORITY_MEDIUM = 20, PRIORITY_HIGH = 30 };.
Sparse codes are poor array indices. labels[PRIORITY_HIGH] would require at least 31 elements, so use a priority_name switch or a table of {code, text} pairs. Two enumerators may share a value, but they cannot produce duplicate case labels in one switch. Prefix names too: two enums that both declare LOW in the same scope collide in C's ordinary identifier namespace.
Typedef aliases: readable declarations without false type safety
Consider a small numeric example:
typedef unsigned long StudentId;
typedef double Score;
StudentId id = 120045UL;
Score marks[3] = {72.0, 84.0, 90.0};
Score average = (marks[0] + marks[1] + marks[2]) / 3.0;
printf("student=%lu average=%.2f\n", id, average);The arithmetic is 72 + 84 + 90 = 246, then 246 / 3 = 82, so the exact output is student=120045 average=82.00.
Read the pattern as typedef old-type new-name;. Here, unsigned long is the existing type and StudentId is its alias. Such aliases clarify a domain role or simplify repeated declarations. They do not add type safety: StudentId and CourseId, if both alias unsigned long, can be assigned to each other. Use separate struct wrappers when the program needs incompatible types.
The practical idiom: combine typedef, enum, and struct
The common combined form lets declarations stay readable while retaining useful tags:
#include <stdio.h>
typedef enum OrderState {
ORDER_NEW = 10,
ORDER_PACKED = 20,
ORDER_SHIPPED = 30
} OrderState;
typedef struct Order {
unsigned long order_id;
OrderState state;
int item_count;
} Order;
const char *order_state_name(OrderState state) {
switch (state) {
case ORDER_NEW: return "new";
case ORDER_PACKED: return "packed";
case ORDER_SHIPPED: return "shipped";
default: return "unknown";
}
}
int main(void) {
Order order = {7001UL, ORDER_PACKED, 3};
printf("order=%lu state=%s code=%d items=%d\n",
order.order_id, order_state_name(order.state),
(int)order.state, order.item_count);
order.state = ORDER_SHIPPED;
printf("order=%lu state=%s code=%d items=%d\n",
order.order_id, order_state_name(order.state),
(int)order.state, order.item_count);
return 0;
}Its exact output is:
order=7001 state=packed code=20 items=3
order=7001 state=shipped code=30 items=3The assignment changes only state from 20 to 30. The order_id stays 7001 and item_count stays 3. In typedef enum OrderState { ... } OrderState;, enum OrderState is the tagged spelling and OrderState is the typedef spelling. Likewise, the next declaration provides both struct Order and Order. These named records and states are useful building blocks when you move into C Programming & Data Structures.

Tags, typedef names, and scope: read the declaration correctly
Tags and typedef names follow different declaration rules:
Spelling | What it means |
|---|---|
| An enum tag and type spelling |
| Valid after |
| A struct tag and type spelling |
| Valid after |
Tags occupy their own namespace, so the tag in struct Node can coexist with the typedef name Node. This makes the following struct forward-declaration pattern useful:
#include <stddef.h>
typedef struct Node Node;
struct Node {
int value;
Node *next;
};
Node first = {42, NULL};Here, first.value is 42 and first.next is a null pointer. Do not present a forward-declared enum as portable C17. Define the enum before code needs its typedef or values. Also avoid aliases that erase meaning, such as an opaque one-letter name for unsigned long.
Common errors: hidden pointers, collisions, invalid inputs, and size assumptions
The pointer trap starts with typedef int *IntPtr;. Then IntPtr a, b; declares two pointers, while int *a, b; declares one pointer and one int. In beginner code, prefer visible int * declarations or one declarator per line when an alias would hide the star.
Enumerator names can also collide. enum Traffic { RED, GREEN }; enum Paint { RED, BLUE }; fails because the second RED reuses an ordinary identifier in the same scope. Repair the names as TRAFFIC_RED, TRAFFIC_GREEN, PAINT_RED, and PAINT_BLUE.
A cast does not validate input. Before assigning a raw integer to OrderState, switch over 10, 20, and 30, and reject 25 or any other value. Finally, never assume sizeof(OrderState) == sizeof(int). The examples keep values in a small int range and cast enum values to int only for %d. For broader practice, use C Programming for Teaching CS Exams and Coding for Placements: C, C++, Java, Python.
How exams test enums and typedef, practice checks, and next step
Exam questions commonly ask you to continue an enum sequence, distinguish a tag from a typedef name, identify a hidden pointer alias, or trace a state change before running code:
enum Mode { A = 3, B, C = 8, D };produces3, 4, 8, 9.After
typedef long Distance; Distance d = 25L;,dis alongalias with value 25.After
typedef int *P; P x, y;, both variables areint *.In the order example, changing
statefrom 20 to 30 leavesorder_idat 7001 anditem_countat 3.
Keep four rules: enum names represent integer values; explicit values control external codes; typedef changes spelling, not type identity; and prefixes plus visible declarations prevent collisions and pointer confusion. Continue with the C Language Course: Concepts, MCQs & Coding when you want enums and typedef placed inside a structured C sequence.
Keep learning

ctype.h Functions in C: Classify Characters and Convert Case Safely
Learn which character test to choose, trace a complete C classifier, and avoid signed-char and EOF mistakes. Worked examples separate classification from case conversion.

Key Features of the C Programming Language: Code Examples
What do C's features actually do in a program? Follow an even-number sum and a pointer update, then test your understanding with small code changes.

Preprocessor and Macros in C: Expansion Rules and Worked Examples
Learn what happens before C compilation, expand object-like and function-like macros by hand, and avoid precedence and repeated-evaluation bugs.

Variables and Data Types in C: Declarations, Conversions and Runnable Examples
Learn how C types control stored values, expressions and I/O. Compile a complete marks program, trace its conversions and repair common type mistakes.