C typedef: Creating Type Aliases
Use typedef to create readable aliases for primitive types, structs, and function pointers, and recognize the common Objective-C/C API patterns — BOOL, NSInteger, and block type aliases — that are built directly on this mechanism.
C's type names can get long, awkward, or simply unclear about intent — unsigned long long, struct Point, or a raw function pointer declaration are all a mouthful compared to a short, purpose-named alias. The typedef keyword solves this by letting you attach a new name to an existing type, and it is one of the most heavily used tools in real C and Objective-C code: BOOL, NSInteger, and the block-based completion handler types you'll meet in later lessons are all, at bottom, ordinary typedefs. This lesson covers what typedef actually does and the handful of patterns you'll see it used for constantly.
What typedef Actually Does
typedef creates an alias for an existing type — it does not create a new, distinct type the way a struct or enum definition does. The general form is typedef <existing type> <new name>;: for example, typedef unsigned long ULong; makes ULong usable anywhere unsigned long could be used, and the compiler treats the two names as completely interchangeable, because underneath they're the exact same type.
This is a purely compile-time convenience — a typedef has zero runtime cost. No memory is allocated, no conversion happens, and no new behavior is introduced; the alias exists only to make source code more readable and more maintainable for humans (and, indirectly, easier for the compiler's error messages and for tools like Xcode's autocomplete to describe precisely).
A typedef'd name behaves exactly like a built-in type name in every context: you can declare variables with it, use it as a function parameter or return type, and cast to it, exactly as you would with int or double.
• typedef <existing type> <new name>; — creates an alias, not a new distinct type. • The alias and the original type are fully interchangeable to the compiler — same size, same behavior. • Zero runtime cost — purely a compile-time/readability convenience. • A typedef'd name can be used anywhere a built-in type name can: variables, parameters, return types, casts.
typedef with Primitive Types
The simplest and most common use is aliasing a primitive type to a name that documents its intended meaning or role. typedef int Age; doesn't change how the compiler treats the value at all — an Age is still exactly an int — but it documents intent when you write Age userAge = 30; in a function signature or variable declaration, making the code more self-explanatory than a bare int would be.
This pattern becomes essential when a type's exact width might need to change later. If a codebase uses typedef int UserID; everywhere instead of writing int directly, and it later turns out int (commonly 32-bit) is too small to hold every user ID the system now needs, changing that single typedef line to typedef long UserID; (or a fixed-width type like int64_t) widens every UserID in the entire codebase in one edit — no need to hunt down and change every individual int UserID declaration by hand.
This exact mechanism is why Objective-C's NSInteger and NSUInteger exist: they are typedefs that resolve to int (32-bit) on 32-bit platforms and long (64-bit) on 64-bit platforms, giving Objective-C code a single portable integer type name that automatically picks the right width for the platform it's compiled for, without any code needing to change.
• typedef int Age; — Age is literally an int, but the name documents intent in code. • Widening a type later (int → long) becomes a one-line edit at the typedef, instead of a codebase-wide find-and-replace. • NSInteger/NSUInteger are typedefs that resolve to different underlying widths (int vs long) depending on the platform. • Using the typedef'd name consistently is what makes that one-line future edit actually work everywhere.
typedef with Structs
As covered in the structs lesson, typedef is used constantly to give struct types a bare, keyword-free name: typedef struct { int x; int y; } Point; lets you write Point p; instead of struct Point p; everywhere afterward. You can also typedef a struct that was already given a tag name separately: struct Point { int x; int y; }; typedef struct Point Point; achieves the same end result through two statements instead of one combined statement.
This is, in practice, the single most common typedef pattern in both plain C and Objective-C code — virtually every struct type you'll encounter in Apple's frameworks (CGPoint, CGRect, CGSize, CGVector, and many more) is defined exactly this way, precisely so application code never has to write the struct keyword when using them.
• typedef struct { ... } Name; — the standard combined form giving an anonymous struct a bare alias. • A struct can also be typedef'd in a separate statement from its definition if it already has a tag name. • This is the most common typedef use in Objective-C/Cocoa — CGPoint, CGRect, CGSize, and similar are all defined this way.
typedef with Function Pointers and Objective-C Block Aliases
C's raw function pointer syntax is notoriously hard to read: a variable that points to a function taking an int and returning an int is declared as int (*funcPtr)(int);, and it only gets messier from there. typedef tames this: typedef int (*IntTransform)(int); creates an alias IntTransform for 'pointer to a function taking an int and returning an int', so afterward you can write IntTransform t = someFunction; instead of repeating the full raw syntax every time such a pointer is declared.
Objective-C extends this same idea to blocks (its closure/lambda-like construct, covered in a later module) with essentially identical syntax: typedef void (^CompletionHandler)(BOOL success); defines CompletionHandler as an alias for 'a block that takes a BOOL and returns nothing'. This is an enormously common pattern in real Objective-C APIs — network requests, animations, and asynchronous operations across Apple's frameworks almost universally accept a typedef'd block-type parameter like this rather than a raw, unreadable inline block type signature repeated at every call site.
The connecting thread through this entire lesson is BOOL itself: typedef signed char BOOL; is how Objective-C's boolean type is actually defined — a typedef over a primitive C type, exactly like the Age or UserID examples earlier, just applied to one of the single most-used types in all of Objective-C. Recognizing that BOOL, NSInteger, and CompletionHandler-style block aliases are all just typedef under the hood means none of Objective-C's 'special' types will feel like new magic once you meet them formally — they're the same one C keyword, applied consistently.
• Raw function pointer syntax is hard to read; typedef gives it a clear name: typedef int (*IntTransform)(int);. • Objective-C blocks use nearly identical typedef syntax: typedef void (^CompletionHandler)(BOOL success);. • Block-type aliases like CompletionHandler are extremely common in real async Objective-C APIs (covered fully in a later module). • BOOL itself is defined as typedef signed char BOOL; — the same mechanism as every other example in this lesson.
typedef Forms: Primitive, Struct, and Function Pointer
The block alias line uses Objective-C's ^ block syntax, introduced here only as a preview of later material. All four typedef forms follow the same core idea: typedef <existing type spelled out> <new short name>;.
typedef unsigned long ULong; // primitive alias
typedef struct { int x; int y; } Point; // struct alias
typedef int (*IntTransform)(int); // function pointer alias
typedef void (^CompletionHandler)(int); // Objective-C block alias (preview)
ULong total = 100;
Point p = { 1, 2 };
IntTransform t = someIntFunction;Code Examples
Age and Score are both aliases for int, so the compiler treats them as fully interchangeable — typedef improves readability for humans but does not add type-checking distinctness the way a struct or enum would. Expected result: Age: 30 Age: 95
Thanks to typedef, Size is used as a bare type name throughout — in the function parameter, the variable declaration, and everywhere else — with no struct keyword required anywhere. Expected result: area = 6.0
CompletionHandler aliases 'a block taking a BOOL and returning nothing', letting performTask's signature read clearly as 'takes a completion handler' rather than repeating raw block syntax — this exact pattern will be covered fully once blocks are introduced. This is a preview only — full block syntax and semantics are taught in a dedicated later lesson; the point here is recognizing the typedef mechanism underneath it.
#include <stdio.h>
typedef int Age;
typedef int Score;
void printAge(Age a) {
printf("Age: %d\n", a);
}
int main(void) {
Age userAge = 30;
Score testScore = 95;
printAge(userAge);
// Age and Score are both really just int - the compiler won't
// stop you from passing a Score where an Age is expected.
printAge(testScore);
return 0;
}#include <stdio.h>
typedef struct {
double width;
double height;
} Size;
double area(Size s) {
return s.width * s.height;
}
int main(void) {
Size box = { 4.0, 3.0 };
printf("area = %.1f\n", area(box));
return 0;
}typedef void (^CompletionHandler)(BOOL success);
void performTask(CompletionHandler completion) {
// ... do some work ...
BOOL didSucceed = YES;
completion(didSucceed);
}
void taskFinished(BOOL success) {
if (success) {
// handle success
}
}Important Rules
Use typedef to alias a primitive type whenever the name should document intent (Age, UserID) rather than exposing a bare int/long everywhere.
Use typedef consistently for a given concept across a codebase so that widening its underlying type later is a true one-line edit.
Prefer the combined typedef struct { ... } Name; form for structs so callers never need to write the struct keyword.
Reach for typedef to tame unreadable function pointer syntax rather than repeating the raw declaration at every use site.
Remember a typedef creates an alias, not a distinct type — it documents intent but does not by itself prevent mixing two conceptually different typedef'd types that share an underlying representation.
One typedef, Every Platform
NSInteger is defined once, per-platform, as either int or long depending on whether the build target is 32-bit or 64-bit. Every Objective-C developer writing NSInteger count = array.count; gets correct, portable behavior automatically on both architectures, without ever needing to know or care which underlying primitive type NSInteger currently resolves to — that's the entire point of hiding a primitive behind a well-named typedef.
// Believing typedef creates a new, incompatible type typedef int Meters; typedef int Seconds; Meters distance = 100; Seconds time = distance; // compiles fine - both are just int!
// Understand typedef is only an alias - use distinct struct wrappers // (or careful naming/review) if you need the compiler to catch this kind of mix-up.
Because Meters and Seconds are both plain typedefs for int, the compiler treats them as fully interchangeable and allows assigning one to the other with no warning — typedef documents intent but does not enforce it.
typedef struct { int x; int y; } Point Point p; // missing semicolon after the typedef statement typedef struct { int x; int y; } Point; Point p;
A typedef statement is a normal C statement and must end with a semicolon just like any other declaration; omitting it is a compile-time syntax error.
What does typedef unsigned long ULong; actually do? A. Creates a brand-new type distinct from unsigned long, with its own type-checking rules B. Creates an alias so ULong and unsigned long are fully interchangeable to the compiler C. Allocates a new global variable named ULong D. Only works inside function bodies, never at file scope
B. Creates an alias so ULong and unsigned long are fully interchangeable to the compiler — typedef never creates a distinct type — it creates an alternate name for an existing type, and the compiler treats the alias and the original as completely interchangeable.
Why is typedef considered valuable for future maintainability, using UserID as an example? A. It makes the program run faster at runtime B. If every declaration uses the typedef'd name consistently, widening the underlying type later (e.g. int to long) becomes a single-line edit instead of a codebase-wide search-and-replace C. It automatically validates that a UserID is never negative D. It prevents the type from ever needing to change
B. If every declaration uses the typedef'd name consistently, widening the underlying type later (e.g. int to long) becomes a single-line edit instead of a codebase-wide search-and-replace — Because a typedef centralizes the real underlying type in one declaration, changing that one line propagates everywhere the alias is used, whereas bare int declarations scattered throughout the code would each need to be found and edited individually.
What is BOOL, as actually defined in Objective-C? A. A distinct built-in language keyword unrelated to typedef B. A typedef over signed char, following the same alias mechanism as any other typedef C. A struct containing a single boolean field D. An enum with YES and NO as its only values
B. A typedef over signed char, following the same alias mechanism as any other typedef — BOOL is defined as typedef signed char BOOL; — it uses the exact same typedef mechanism covered throughout this lesson, just applied to one of Objective-C's most fundamental types.
What does typedef void (^CompletionHandler)(BOOL success); alias? A. A struct with a BOOL field named success B. An Objective-C block type that takes a BOOL parameter and returns nothing C. A function pointer that returns a BOOL D. A global variable named CompletionHandler
B. An Objective-C block type that takes a BOOL parameter and returns nothing — The ^ syntax denotes an Objective-C block; this typedef names the type 'a block taking one BOOL parameter and returning void', a pattern used constantly for completion handlers in asynchronous Objective-C APIs.
Write a typedef named Temperature that aliases double, and a typedef named TemperatureConverter for a function pointer type taking a double and returning a double. Implement a function celsiusToFahrenheit matching that signature, declare a TemperatureConverter variable pointing to it, and call it through the variable with 100.0, printing the result.
The function pointer typedef form is typedef double (*Name)(double); — mirror the IntTransform example but with double instead of int.