Published: 19 Nov 2025 | Reading Time: 7 min read
Have you ever written a C program that worked, but became painfully slow the moment you added more data?
Or faced errors you couldn't explain because everything felt "too mixed up" in your logic?
Or wondered why senior developers write cleaner, faster programs with half the effort?
The difference is never "more experience." It's data structures — the part of C programming that decides how efficiently your code stores, accesses, and manages data.
If you're building programs that freeze with large input, take too long to search a list, or become impossible to scale, the real problem is simple:
You're using the wrong data structure.
This blog gives you a clear, practical, student-friendly guide to understanding exactly how and why these structures work. You'll see how arrays, linked lists, stacks, queues, trees, and graphs actually solve real problems — not theoretically, but in the same way they're used in operating systems, compilers, networks, and real applications.
Data structures provide systematic ways to store, organize, and manage data, making operations like searching, inserting, or deleting faster. Algorithms are the step-by-step procedures that operate on these structures to perform tasks efficiently.
Together, DSA in C determines:
From simple arrays to complex graphs, each structure brings a new way of thinking about problems.
In C, there are two main types of algorithms to learn and understand, which are the basis of data structures in C. Understanding these two types of core algorithms will be worthwhile in building flow and performance in applications.
The C programming language provides a variety of built-in data types and also provides the flexibility to create custom, user-defined types. These data structures in C can be divided into two main categories: primitive and non-primitive data structures.
Primitive data structures are the basic types provided by C. These are simple and fundamental building blocks for creating more complex structures in Data Structures in C:
Non-primitive data structures are more complicated structures and are built using primitive data types. These allow for storing and organizing large sets of data in more flexible ways.
Here is the detailed overview of common data structures in C that you should know to acquire a complete understanding of data structures, as they allow efficient storage, manipulation, and retrieval of data.
Arrays in C are one of the simplest and most commonly used data structures. Arrays provide an efficient way to store a fixed collection of elements, and each element can be accessed via an index.
In C, an array can be declared by specifying its type by the array name and the number of elements it will contain.
int numbers[5] = {1, 2, 3, 4, 5};
#include <stdio.h>
int main() {
// Declaration and initialization of the array
int numbers[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
printf("%d\n", numbers[i]);
}
return 0;
}
1
2
3
4
5
A linked list is a linear data structure that is made up of a series of nodes. Each node has data and a pointer (reference) to the next node in the series. Concerning an array, a linked list does not require contiguous memory to store its elements, and each node can be stored in different places in memory. The list is still conceptually maintained through a set of pointers.
#include <stdio.h>
#include <stdlib.h>
// Define the structure of a node
struct Node {
int data; // Store the data in the node
struct Node* next; // Pointer to the next node
};
// Function to create a new node
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // Allocate memory for a new node
newNode->data = data; // Assign the data
newNode->next = NULL; // Set the next pointer to NULL (end of the list)
return newNode; // Return the new node
}
// Function to print the linked list
void printList(struct Node* head) {
struct Node* temp = head; // Start from the head
while (temp != NULL) { // Traverse through the list
printf("%d ", temp->data); // Print the data of each node
temp = temp->next; // Move to the next node
}
}
int main() {
// Create a linked list with three nodes
struct Node* head = createNode(1);
head->next = createNode(2); // Link first node to second node
head->next->next = createNode(3); // Link second node to third node
// Print the linked list
printList(head); // Output should be: 1 2 3
return 0;
}
1 2 3
A stack data structure is a Last In, First Out (LIFO) structure—the last place you put an element in the stack is the first to be removed from the stack. Stacks are often used in situations, such as function calls, the undo feature of apps, and parsing expressions.
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
// Structure to represent a stack
struct Stack {
int top; // Index of the top element
int items[MAX]; // Array to store stack elements
};
// Function to push an element onto the stack
void push(struct Stack* stack, int item) {
if (stack->top == MAX - 1) {
printf("Stack Overflow\n");
return; // Return if the stack is full
}
stack->items[++stack->top] = item; // Increment top and add item
}
int pop(struct Stack* stack) {
if (stack->top == -1) {
printf("Stack Underflow\n");
return -1; // Return -1 if the stack is empty
}
return stack->items[stack->top--]; // Return the top element and decrement top
}
int main() {
struct Stack stack;
stack.top = -1; // Initialize stack with an empty state
push(&stack, 10); // Push 10 onto the stack
push(&stack, 20); // Push 20 onto the stack
printf("%d popped from stack\n", pop(&stack)); // Pop the top element and print it
return 0;
}
20 popped from stack
A queue is a linear data structure that functions based on a First In, First Out (FIFO) principle. The first element you put in the queue is the first element removed from the queue, which is just like being in a line at a ticket counter.
#include <stdio.h>
#include <stdlib.h>
#define MAX 5 // Maximum size of the queue
struct Queue {
int front, rear;
int items[MAX];
};
void enqueue(struct Queue* queue, int item) {
if (queue->rear == MAX - 1) {
printf("Queue Overflow\n");
return;
}
queue->items[++queue->rear] = item;
}
int dequeue(struct Queue* queue) {
if (queue->front > queue->rear) {
printf("Queue Underflow\n");
return -1; // Indicates empty queue
}
return queue->items[queue->front++];
}
int main() {
struct Queue queue;
queue.front = queue.rear = -1;
enqueue(&queue, 10);
enqueue(&queue, 20);
printf("%d dequeued from queue\n", dequeue(&queue));
return 0;
}
10 dequeued from queue
Advanced data structures in C offer more sophisticated ways to represent and process data. These data structures are ideal when you need to work with more complex relationships or when managing large volumes of data efficiently.
A tree is a non-linear, hierarchical data structure comprised of nodes connected by edges. The topmost node is referred to as the root node. A node can have zero or more children; however, in a binary tree, a node has at most two child nodes, which are called a left child and a right child. Trees are important for representing hierarchical relationships between data, like file systems and organizational charts.
Each node of a binary tree will typically contain:
A binary search tree is a special type of binary tree that has the property that:
This property makes searching, inserting, and removing in the tree more efficient.
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* left;
struct Node* right;
};
struct Node* createNode(int data) {
struct Node* node = (struct Node*)malloc(sizeof(struct Node));
node->data = data;
node->left = node->right = NULL;
return node;
}
void inorder(struct Node* root) {
if (root != NULL) {
inorder(root->left);
printf("%d ", root->data);
inorder(root->right);
}
}
int main() {
struct Node* root = createNode(10);
root->left = createNode(5);
root->right = createNode(15);
printf("In-order traversal: ");
inorder(root); // Output: 5 10 15
return 0;
}
A heap is a specialized tree-based data structure that satisfies the heap property. Heaps are most commonly implemented as arrays, allowing efficient access and manipulation of elements.
#include <stdio.h>
#define MAX 100
int heap[MAX];
int size = 0;
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// Insert a new element into the heap
void insert(int value) {
if (size >= MAX) {
printf("Heap overflow\n");
return;
}
heap[size] = value;
int i = size;
size++;
while (i != 0 && heap[(i - 1) / 2] < heap[i]) {
swap(&heap[(i - 1) / 2], &heap[i]);
i = (i - 1) / 2;
}
}
// Heapify down from index i
void heapifydown(int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < size && heap[left] > heap[largest])
largest = left;
if (right < size && heap[right] > heap[largest])
largest = right;
if (largest != i) {
swap(&heap[i], &heap[largest]);
heapifydown(largest);
}
}
// Remove and return the maximum element (root)
int extractmax() {
if (size == 0) {
printf("Heap underflow\n");
return -1;
}
int root = heap[0];
heap[0] = heap[size - 1];
size--;
heapifydown(0);
return root;
}
A graph is a non-linear data structure representing a collection of vertices (also referred to as nodes) and a collection of edges that link pairs of vertices. Graphs are excellent for modeling relationships or networks, such as social networks, transportation systems, and communication networks.
Graphs can be represented in several different ways, however, the most common representations are an adjacency list (suggested) or an adjacency matrix (less preferred) is one way to represent the structure of a graph. Adjacency list is preferred as an efficient sparse graph representation.
#include <stdio.h>
#include <stdlib.h>
struct Node {
int vertex;
struct Node* next;
};
struct Graph {
int numVertices;
struct Node** adjLists;
};
struct Node* createNode(int v) {
struct Node* newNode = malloc(sizeof(struct Node));
newNode->vertex = v;
newNode->next = NULL;
return newNode;
}
struct Graph* createGraph(int vertices) {
struct Graph* graph = malloc(sizeof(struct Graph));
graph->numVertices = vertices;
graph->adjLists = malloc(vertices * sizeof(struct Node*));
for (int i = 0; i < vertices; i++)
graph->adjLists[i] = NULL;
return graph;
}
void addEdge(struct Graph* graph, int src, int dest) {
// Add edge from src to dest
struct Node* newNode = createNode(dest);
newNode->next = graph->adjLists[src];
graph->adjLists[src] = newNode;
// Add edge from dest to src (undirected graph)
newNode = createNode(src);
newNode->next = graph->adjLists[dest];
graph->adjLists[dest] = newNode;
}
void printGraph(struct Graph* graph) {
for (int v = 0; v < graph->numVertices; v++) {
struct Node* temp = graph->adjLists[v];
printf("Vertex %d: ", v);
while (temp) {
printf("%d -> ", temp->vertex);
temp = temp->next;
}
printf("NULL\n");
}
}
int main() {
struct Graph* graph = createGraph(4);
addEdge(graph, 0, 1);
addEdge(graph, 0, 2);
addEdge(graph, 1, 2);
addEdge(graph, 2, 3);
printGraph(graph);
return 0;
}
Hashing is a method for mapping data (for example, keys) to a fixed-size value (the "hash value/digest") using a mathematical method (the "hash function"). This process leads to more efficient searches, inserts and deletes in data structures such as "hash tables".
#include <stdio.h>
#include <stdlib.h>
#define TABLE_SIZE 10
struct Node {
int key;
struct Node* next;
};
struct Node* hashTable[TABLE_SIZE];
// Hash function
int hashFunction(int key) {
return key % TABLE_SIZE;
}
// Insert key
void insert(int key) {
int index = hashFunction(key);
struct Node* newNode = malloc(sizeof(struct Node));
newNode->key = key;
newNode->next = hashTable[index];
hashTable[index] = newNode;
}
// Search key
int search(int key) {
int index = hashFunction(key);
struct Node* temp = hashTable[index];
while (temp) {
if (temp->key == key)
return 1;
temp = temp->next;
}
return 0;
}
// Delete key
void delete(int key) {
int index = hashFunction(key);
struct Node* temp = hashTable[index];
struct Node* prev = NULL;
while (temp) {
if (temp->key == key) {
if (prev)
prev->next = temp->next;
else
hashTable[index] = temp->next;
free(temp);
return;
}
prev = temp;
temp = temp->next;
}
}
A pointer is a variable that contains a memory address instead of the actual data value. Pointers are the basis of C programming when dealing with dynamic data; data can be accessed via a pointer rather than its value. For example, in the context of Data Structures in C, Pointers allow access to memory efficiently and manipulate the data without caching large data values.
#include <stdio.h>
#include <stdlib.h>
// Define the structure of a node in the linked list
struct Node {
int data;
struct Node* next;
};
int main() {
// Dynamically allocate memory for the first node
struct Node* head = (struct Node*)malloc(sizeof(struct Node));
// Assign data to the node
head->data = 10;
// Set the next pointer to NULL (indicating the end of the list)
head->next = NULL;
// Print the data stored in the node
printf("Data: %d", head->data);
// Free the allocated memory
free(head);
return 0;
}
Data: 10
Pointers are the driving forces behind dynamic data structures in C. They let you create dynamic structures, like linked lists, trees, and graphs, without the overhead of duplicating large pieces of data. You must understand pointers. They are what make simple programs into larger, real-world systems, which are usually much more complicated.
Structures and unions are user-defined data types in C that allow you to group different types of variables together. They allow you to store a collection of data of different types. However, they differ in terms of memory management and usage in some significant ways.
A structure provides you the ability to group the variables of different types into one variable using a single name to access them. This is convenient when you are trying to represent a collection of related data such as characteristics about a person, book or product.
#include <stdio.h>
struct Person {
char name[50];
int age;
float salary;
};
int main() {
struct Person person1 = {"John Doe", 30, 50000.50};
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Salary: %.2f\n", person1.salary);
return 0;
}
Name: John Doe
Age: 30
Salary: 50000.50
A union allows multiple data types to be stored in the same memory location. However, only one value can be stored at any given time. The memory used by a union is the size of its largest member, and all members share the same memory space.
#include <stdio.h>
union Data {
int intValue;
float floatValue;
char charValue;
};
int main() {
union Data data;
data.intValue = 10;
printf("Integer: %d\n", data.intValue);
data.floatValue = 3.14;
printf("Float: %.2f\n", data.floatValue);
data.charValue = 'A';
printf("Character: %c\n", data.charValue);
return 0;
}
Integer: 10
Float: 3.14
Character: A
The difference between structures and union in C is that structures let you store multiple related values together, while unions let you save memory by storing different values in the same location, but only one at a time. Use structures when you need to keep all fields, and unions when memory efficiency matters. Together, they give C the flexibility to model real-world data effectively.
Data structures in C are fundamental to many areas of computer science and used in combinations of applications to help solve real-world problems efficiently. In C, these structures are of fundamental importance in several areas.
Before you move to more advanced programming, ask yourself one thing: Are you writing code that works… or code that performs? Anyone can build a program that runs, but only a developer who understands data structures can build one that scales, responds quickly, and handles real-world complexity without breaking.
Every structure you learned — arrays, linked lists, stacks, queues, trees, graphs, hashing, pointers, structures — exists because real problems demand real organization. When data grows, when time matters, when memory becomes tight, the right data structure is what saves your program.
So the real question is: do you want code that just solves the problem today, or would you prefer code that just solves the problem today and still executes efficiently tomorrow?
Understanding data structures in C is how you assimilate into that next level.
Data structures in C facilitate the efficient management and storage of data. Acquaintance with data structures like arrays, linked lists, stacks, queues, trees, and graphs is crucial, as different data structures are needed for different tasks in programming.
An array is a data structure that stores elements in a contiguous block of memory of a fixed size. A linked list is comprised of elements stored in nodes linked together by pointers, which allows for the dynamic size of a data structure, while also slower access due to its more complicated nature.
Pointers allow for the storage of memory addresses of variables of values. Data structures such a linked list are advantageous because each group, or node, can refer to un-related data, which allows for efficient data usage while retaining the ability to link the data.
A stack implements data on a Last In First Out (LIFO) basis. There are many applications of a stack including managing function calls, evaluating expressions, and backtracking algorithms.
A queue is a different data structure that operates based on a first-come, first-serve basis. Similar to a stack, there are many potential uses of a queue, including managing the printing of documents, scheduling a process, and handling a data flow.
A tree shows the hierarchical relationship of an arrangement of data. A tree can be used in applications such as a database, and in an organizational cycle, such as a file system tree. Trees can also illustrate algorithms very effectively in sorting and searching.
Dynamic data structures can grow or shrink during execution, like linked lists, while static ones, like arrays, have a fixed size set at compile time.
Source: NxtWave - https://www.ccbp.in/blog/articles/data-structures-in-c