# Why Declaring a `struct node` Definition is Critical for C++ Linked Lists

> Learn why declaring a struct node in C++ is critical for linked lists. Understand type-safe traversal, predictable allocation, and extensible operations for efficient data management.

- Repository: [Python/cpython](https://github.com/python/cpython)
- Tags: deep-dive
- Published: 2026-02-20

---

**Declaring a `struct node` definition in C++ encapsulates data and linkage pointers into a single memory unit, enabling type-safe traversal, predictable allocation, and extensible list operations.**

When implementing linked lists in C++, the `struct node` declaration serves as the architectural foundation that determines how data is stored, linked, and managed in memory. This pattern appears throughout high-performance codebases like the CPython repository, where internal linked-list implementations rely on carefully defined node structures to manage everything from thread states to garbage collection cycles.

## Core Functions of a `struct node` Definition

### Encapsulates Data and Linkage Pointers

The primary role of a `struct node` is to group the payload data with pointers to adjacent nodes. In [`Modules/_threadmodule.c`](https://github.com/python/cpython/blob/main/Modules/_threadmodule.c), CPython defines `struct llist_node` to create a singly-linked list for thread runtime management:

```c
/* Modules/_threadmodule.c – line 126 */
struct llist_node {
    struct llist_node *next;   /* linked list node */
    /* payload fields follow */
};

```

This encapsulation creates a single, well-defined unit that can be allocated, copied, and freed as one contiguous memory block.

### Defines Memory Layout and Alignment

Declaring the struct allows the compiler to calculate `sizeof(struct node)`, determining the exact memory footprint and alignment requirements. This predictability is essential for manual memory management using `malloc` in C or `new` in C++.

In CPython's garbage collector ([`Python/gc.c`](https://github.com/python/cpython/blob/main/Python/gc.c)), the `PyGC_Head` struct demonstrates this precise layout control:

```c
/* Python/gc.c – line 199 */
typedef struct _gc_head {
    struct _gc_head *gc_prev;
    struct _gc_head *gc_next;
    /* additional GC metadata */
} PyGC_Head;

```

The compiler knows the exact size of this structure, enabling the GC to traverse the doubly-linked list of live objects by pointer arithmetic.

### Enables Type Safety

A declared `struct node` creates a distinct type that the compiler can validate. Functions that manipulate the list accept `struct node *` arguments, guaranteeing that only properly typed node objects are passed.

For example, in [`Objects/odictobject.c`](https://github.com/python/cpython/blob/main/Objects/odictobject.c), the ordered dictionary implementation uses `ODictNode` to maintain insertion order:

```c
/* Objects/odictobject.c – lines 490-491 */
typedef struct _odictnode {
    struct _odictnode *od_next;
    struct _odictnode *od_prev;
    PyObject *od_key;
    PyObject *od_value;
} ODictNode;

```

Functions operating on these nodes use `ODictNode *` types, preventing accidental mixing with other pointer types and catching errors at compile time rather than runtime.

### Facilitates Extensibility

The struct definition provides a stable interface that can accommodate additional fields without changing the surrounding algorithm. You can extend a singly-linked list to a doubly-linked list by adding a `prev` pointer, or include reference counts and debugging information.

CPython's evolution demonstrates this pattern: the `PyGC_Head` struct extends the basic node concept with garbage collection metadata, while `ODictNode` adds key-value storage to the linkage structure.

## Practical C++ Implementation Patterns

While CPython is implemented in C, these patterns translate directly to C++. In C++, you declare the node structure using either `struct` or `class`:

```cpp
// Singly-linked list node
struct Node {
    int data;
    Node* next;
    
    Node(int val) : data(val), next(nullptr) {}
};

```

For a doubly-linked list, extend the structure with a previous pointer, similar to CPython's `ODictNode`:

```cpp
// Doubly-linked list node
struct DoublyNode {
    int data;
    DoublyNode* prev;
    DoublyNode* next;
    
    DoublyNode(int val) : data(val), prev(nullptr), next(nullptr) {}
};

```

The `struct` keyword in C++ defaults to public members, making it syntactically cleaner for simple data containers compared to `class`, though both generate identical machine code when access specifiers are matched.

## Summary

- **Encapsulation**: A `struct node` groups data and linkage pointers into a single allocatable unit, as demonstrated by CPython's `llist_node` and `ODictNode` implementations in [`Modules/_threadmodule.c`](https://github.com/python/cpython/blob/main/Modules/_threadmodule.c) and [`Objects/odictobject.c`](https://github.com/python/cpython/blob/main/Objects/odictobject.c).
- **Memory Layout**: Explicit struct declarations enable the compiler to calculate `sizeof(struct node)`, ensuring predictable allocation and alignment for manual memory management.
- **Type Safety**: Distinct struct types prevent pointer mismatches, with functions accepting `struct node *` arguments to enforce correct usage at compile time.
- **Extensibility**: The struct pattern supports evolution from singly-linked to doubly-linked lists by adding fields like `prev` pointers without breaking existing traversal algorithms.

## Frequently Asked Questions

### Can I use a class instead of struct for linked list nodes in C++?

Yes, you can use either `class` or `struct` to define linked list nodes in C++. The only difference is default access control: `struct` defaults to public members while `class` defaults to private. For simple data containers like list nodes, `struct` is often preferred for brevity, but both generate identical machine code when you explicitly specify access specifiers.

### How does the compiler determine the size of a struct node?

The compiler calculates `sizeof(struct node)` by summing the sizes of all member variables plus any padding bytes required for memory alignment. For a typical node containing an `int` and a pointer, the size would be `sizeof(int) + sizeof(void*)` plus alignment padding. This predictable sizing enables proper memory allocation using `new` or `malloc` and correct pointer arithmetic during list traversal.

### What happens if I don't declare the struct node before using it in function prototypes?

If you use `struct node*` in a function prototype without a prior declaration, the compiler will raise an error about an unknown type name. In C++, you must either define the struct before the function prototype or use a forward declaration like `struct Node;` to inform the compiler that the type exists. This requirement enforces type safety and ensures the compiler knows the proper pointer size and alignment for the struct.

### Why do some implementations use typedef with struct node?

The `typedef` keyword creates an alias for the struct type, allowing you to declare variables without the `struct` keyword. For example, `typedef struct node { int data; struct node* next; } Node;` lets you write `Node* head` instead of `struct node* head`. This is primarily a stylistic choice that reduces typing in C; in C++, the `struct` keyword is optional when declaring variables, making `typedef` less necessary though still common for consistency with C codebases.