List of structs in C: solved exercise

List of structs in C: solved exercise

If you searched for a solved list of structs in C, here is the singly linked list where each node contains a Contact struct and a pointer to the next node. This pattern combines pointer management with structured data: both the struct’s dynamic fields and the node itself must be freed.

Problem statement

Define a Contact struct with fields name (32-character array), phone (16-character array), and next (pointer to the next node). Implement:

  1. list_append(head, name, phone): inserts a node at the end.
  2. list_find(head, name): returns the pointer to the node with that name, or NULL.
  3. list_remove(head, name): deletes the node with that name.
  4. list_print(head): prints all contacts.
  5. list_free(head): frees all nodes.

C solution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_NAME  32
#define MAX_PHONE 16

typedef struct Contact {
    char name[MAX_NAME];
    char phone[MAX_PHONE];
    struct Contact *next;
} Contact;

Contact *list_append(Contact *head, const char *name, const char *phone) {
    Contact *node = malloc(sizeof(Contact));
    if (!node) return head;

    strncpy(node->name,  name,  MAX_NAME  - 1); node->name[MAX_NAME   - 1] = '\0';
    strncpy(node->phone, phone, MAX_PHONE - 1); node->phone[MAX_PHONE - 1] = '\0';
    node->next = NULL;

    if (!head) return node;

    Contact *cur = head;
    while (cur->next) cur = cur->next;
    cur->next = node;
    return head;
}

Contact *list_find(Contact *head, const char *name) {
    for (Contact *cur = head; cur; cur = cur->next)
        if (strncmp(cur->name, name, MAX_NAME) == 0) return cur;
    return NULL;
}

Contact *list_remove(Contact *head, const char *name) {
    Contact *prev = NULL, *cur = head;
    while (cur) {
        if (strncmp(cur->name, name, MAX_NAME) == 0) {
            if (prev) prev->next = cur->next;
            else       head      = cur->next;
            free(cur);
            return head;
        }
        prev = cur; cur = cur->next;
    }
    return head;   /* not found */
}

void list_print(const Contact *head) {
    int i = 1;
    for (const Contact *cur = head; cur; cur = cur->next, i++)
        printf("  %d. %-20s %s\n", i, cur->name, cur->phone);
}

void list_free(Contact *head) {
    Contact *cur = head;
    while (cur) {
        Contact *next = cur->next;
        free(cur);
        cur = next;
    }
}

int main(void) {
    Contact *list = NULL;

    list = list_append(list, "Alice Martin",  "600-111-222");
    list = list_append(list, "Bob Johnson",   "611-333-444");
    list = list_append(list, "Carol Smith",   "622-555-666");
    list = list_append(list, "David Brown",   "633-777-888");

    printf("Full list:\n");
    list_print(list);

    Contact *c = list_find(list, "Bob Johnson");
    printf("\nFind 'Bob Johnson': %s\n", c ? c->phone : "not found");

    printf("\nRemoving 'Alice Martin'...\n");
    list = list_remove(list, "Alice Martin");
    list_print(list);

    printf("\nRemoving 'David Brown'...\n");
    list = list_remove(list, "David Brown");
    list_print(list);

    list_free(list);
    return 0;
}

Expected output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Full list:
  1. Alice Martin          600-111-222
  2. Bob Johnson           611-333-444
  3. Carol Smith           622-555-666
  4. David Brown           633-777-888

Find 'Bob Johnson': 611-333-444

Removing 'Alice Martin'...
  1. Bob Johnson           611-333-444
  2. Carol Smith           622-555-666
  3. David Brown           633-777-888

Removing 'David Brown'...
  1. Bob Johnson           611-333-444
  2. Carol Smith           622-555-666

Common mistakes

  • Not returning the new head when deleting the first node: if the node to delete is the head, head = cur->next must be updated and the new value returned to the caller.
  • Freeing cur before saving cur->next: after freeing, accessing cur->next is undefined behavior.
  • Not updating the previous node’s next pointer when removing: leaves a dangling pointer pointing to the freed node.
  • Not freeing all nodes at the end: each node was allocated with malloc; the list must be traversed and each node freed.

Practical use

Linked lists of structs are the basic pattern of many real data structures: hash tables with chaining for collision resolution, event queues in operating systems, plugin managers that dynamically add/remove modules, and LRU caches where the oldest entry is reordered or removed.

Guided practice and full book

If you want a complete path with progressive difficulty:

FAQ

Why do list_remove and list_append return the new head?

Because C has no references: if the head changes (first node deleted, or insertion into an empty list), the caller must receive the new pointer. The idiomatic pattern is list = list_remove(list, name).

When should a linked list be used instead of a dynamic array?

A linked list is preferable when insertions/deletions in the middle are frequent (O(1) with the pointer to the previous node) and random access is not needed. A dynamic array is better when index-based access (O(1)) is required or when contiguous memory matters for cache performance.

How do you detect memory leaks in linked lists?

With Valgrind: valgrind --leak-check=full ./program. Each node allocated with malloc that is not freed will appear as “definitely lost”. AddressSanitizer (gcc -fsanitize=address) also detects accesses to already-freed memory (use-after-free).