Structures with Functions & Pointers
0%
C Programming  ·  Struct + Functions + Pointers

Structures with
Functions & Pointers

10 simple examples showing how to pass structs to functions, modify them with pointers, use the arrow operator, and build a small student record mini-project.

1
Pass struct to function
2
Return struct
3
Pointer to struct
4
Modify via pointer
5
Arrow operator
6
Array of structs
7
Display function
8
Find topper
9
Update via pointer
10
Mini project
Three ways to pass a struct to a function:

1. void display(Student s) — pass by value — function gets a copy. Cannot change the original.
2. void update(Student *s) — pass by pointer — function can change the original.
3. Student create() — function returns a whole struct back to the caller.
dot operator — direct variable Student s;
s.name = "Ananta"
s.marks = 88.5

display(s) ← pass copy
s.age = 18 ← direct access
arrow operator — pointer to struct Student *p = &s;
p->name = "Ananta"
p->marks = 88.5

display(p) ← pass pointer
p->age = 18 ← same as (*p).age
example 1
1
Pass a Struct to a Function — By Value
Function receives a copy — prints it — original is unchanged
Pass by value
The simplest way to use a struct with a function. Pass the whole struct to display(). The function gets a copy — it can read the fields and print them, but any changes it makes do NOT affect the original in main.
ex1_pass_value.c
C
#include <stdio.h>
#include <string.h>

typedef struct {
    char  name[20];
    int   roll;
    float marks;
} Student;

/* receives a COPY of the struct */
void display(Student s) {
    printf("Name  : %s\n",  s.name);
    printf("Roll  : %d\n",  s.roll);
    printf("Marks : %.1f\n", s.marks);
}

int main() {
    Student s1 = {"Ananta", 101, 87.5};
    Student s2 = {"Priya",  102, 92.0};

    printf("--- Student 1 ---\n");
    display(s1);

    printf("--- Student 2 ---\n");
    display(s2);

    return 0;
}
output
--- Student 1 ---
Name  : Ananta
Roll  : 101
Marks : 87.5
--- Student 2 ---
Name  : Priya
Roll  : 102
Marks : 92.0
display(s1) — the entire struct s1 is copied into the function's parameter s. All three fields are available inside the function using the dot operator.
example 2
2
Function Returns a Struct
create() builds a struct and sends it back to main
Return struct
A function can return a whole struct. The create() function takes a name, roll, and marks — fills a local Student struct — and returns it. main receives the complete struct. This is the clean way to build a struct inside a function.
ex2_return_struct.c
C
#include <stdio.h>
#include <string.h>

typedef struct {
    char  name[20];
    int   roll;
    float marks;
} Student;

/* Function builds and RETURNS a struct */
Student create(char n[], int r, float m) {
    Student s;
    strcpy(s.name, n);
    s.roll  = r;
    s.marks = m;
    return s;   /* return the whole struct */
}

void display(Student s) {
    printf("%s  Roll:%d  Marks:%.1f\n", s.name, s.roll, s.marks);
}

int main() {
    Student a = create("Ananta", 101, 87.5);
    Student b = create("Rahul",  103, 65.0);
    Student c = create("Sneha",  104, 95.0);

    display(a);
    display(b);
    display(c);
    return 0;
}
output
Ananta  Roll:101  Marks:87.5
Rahul   Roll:103  Marks:65.0
Sneha   Roll:104  Marks:95.0
example 3
3
Pointer to a Struct — The Arrow Operator
int *p points to int — Student *p points to Student
Pointer + struct
Just like int *p = &x makes p point to an integer, Student *p = &s makes p point to a struct. To access fields through a pointer you use p->name instead of p.name. The arrow -> means "go to the struct this pointer points to, then access the field."
ex3_struct_pointer.c
C
#include <stdio.h>

typedef struct {
    char  name[20];
    int   roll;
    float marks;
} Student;

int main() {
    Student  s = {"Ananta", 101, 87.5};
    Student *p = &s;   /* p points to s */

    /* Both ways give the same result */
    printf("Using dot    : %s  %d  %.1f\n",
           s.name,    s.roll,    s.marks);

    printf("Using arrow  : %s  %d  %.1f\n",
           p->name,   p->roll,   p->marks);

    printf("Using (*p).  : %s  %d  %.1f\n",
           (*p).name, (*p).roll, (*p).marks);

    printf("\np->marks is same as s.marks: %s\n",
           (p->marks == s.marks) ? "YES" : "NO");

    return 0;
}
output
Using dot    : Ananta  101  87.5
Using arrow  : Ananta  101  87.5
Using (*p).  : Ananta  101  87.5

p->marks is same as s.marks: YES
p->marks is just shorthand for (*p).marks. Both do the same thing — go to the struct p points to and read the marks field. The arrow is cleaner to write and much easier to read.
example 4
4
Modify a Struct Through a Pointer
Pass &s to function — function changes the original using p->field
Modify via ptr
When you pass &s to a function, the function receives a pointer and can change the original struct. This is pass by pointer — the same idea as with int pointers, just applied to a struct. The giveBonus() function adds 5 marks to the original student.
ex4_modify_pointer.c
C
#include <stdio.h>

typedef struct {
    char  name[20];
    float marks;
} Student;

/* Receives pointer — can change the ORIGINAL */
void giveBonus(Student *p, float bonus) {
    p->marks += bonus;   /* changes the real struct */
    printf("Bonus given to %s. New marks: %.1f\n",
           p->name, p->marks);
}

int main() {
    Student s = {"Rahul", 65.0};

    printf("Before: %s = %.1f\n", s.name, s.marks);

    giveBonus(&s, 10.0);   /* pass address of s */

    printf("After : %s = %.1f\n", s.name, s.marks);
    return 0;
}
output
Before: Rahul = 65.0
Bonus given to Rahul. New marks: 75.0
After : Rahul = 75.0
p->marks += bonus — the -> goes to the actual struct in memory and updates it directly. After the function returns, s.marks in main is 75.0 — changed permanently.
example 5
5
Dot vs Arrow — Side by Side
Same struct, same data — two ways to access depending on what you have
Dot vs Arrow
Simple rule: if you have the struct variable directly — use . (dot). If you have a pointer to the struct — use -> (arrow). Two functions doing the same job — one takes the struct, one takes a pointer.
ex5_dot_vs_arrow.c
C
#include <stdio.h>

typedef struct {
    char  name[20];
    int   age;
    float salary;
} Employee;

/* Has the struct → uses dot */
void showDot(Employee e) {
    printf("[dot]   %s  age:%d  Rs%.0f\n",
           e.name, e.age, e.salary);
}

/* Has a pointer → uses arrow */
void showArrow(Employee *e) {
    printf("[arrow] %s  age:%d  Rs%.0f\n",
           e->name, e->age, e->salary);
}

int main() {
    Employee emp = {"Ananta", 28, 45000};

    showDot(emp);      /* pass the struct */
    showArrow(&emp);   /* pass the address */

    return 0;
}
output
[dot]   Ananta  age:28  Rs45000
[arrow] Ananta  age:28  Rs45000
Both produce the same output. The only difference is what you pass: showDot(emp) copies the whole struct — showArrow(&emp) passes just the address (8 bytes on a 64-bit system). For large structs, passing a pointer is much faster than copying.
example 6
6
Array of Structs — Passed to a Function
Pass the whole array to a display function — loop through with pointer
Array of structs
An array of structs is passed to a function as a pointer — Student *arr inside the function refers to the whole array. Use arr[i].field inside the function to access each element. The size n is passed separately because the function doesn't know the array length.
ex6_array_structs.c
C
#include <stdio.h>

typedef struct {
    char  name[20];
    int   roll;
    float marks;
} Student;

/* Array of structs passed as pointer */
void displayAll(Student *arr, int n) {
    int i;
    printf("%-10s %-6s %s\n", "Name", "Roll", "Marks");
    printf("----------------------------\n");
    for (i = 0; i < n; i++) {
        printf("%-10s %-6d %.1f\n",
               arr[i].name, arr[i].roll, arr[i].marks);
    }
}

int main() {
    Student s[4] = {
        {"Ananta", 101, 87.5},
        {"Priya",  102, 92.0},
        {"Rahul",  103, 65.5},
        {"Sneha",  104, 95.0}
    };

    displayAll(s, 4);   /* array name = pointer to first element */
    return 0;
}
output
Name       Roll   Marks
----------------------------
Ananta     101    87.5
Priya      102    92.0
Rahul      103    65.5
Sneha      104    95.0
example 7
7
Grade Function — Takes Struct, Returns char
Function reads marks from struct and returns 'A', 'B', 'C', or 'F'
Return value
A function takes a Student struct, reads the marks field, and returns a grade character. Shows that a function receiving a struct can use its fields in any calculation and return a simple value.
ex7_grade.c
C
#include <stdio.h>

typedef struct {
    char  name[20];
    float marks;
} Student;

/* Takes struct, returns grade char */
char getGrade(Student s) {
    if      (s.marks >= 90) return 'A';
    else if (s.marks >= 75) return 'B';
    else if (s.marks >= 55) return 'C';
    else                     return 'F';
}

int main() {
    Student students[4] = {
        {"Ananta", 87.5},
        {"Priya",  92.0},
        {"Rahul",  52.0},
        {"Sneha",  95.0}
    };
    int i;

    for (i = 0; i < 4; i++) {
        printf("%-10s %.1f  Grade: %c\n",
               students[i].name,
               students[i].marks,
               getGrade(students[i]));
    }
    return 0;
}
output
Ananta     87.5  Grade: B
Priya      92.0  Grade: A
Rahul      52.0  Grade: F
Sneha      95.0  Grade: A
example 8
8
Find the Topper — Returns Pointer to Best Struct
Function scans array, returns Student* pointing to the topper
Return pointer
The function loops through the array, keeps track of the pointer to the best student, and returns that pointer. The caller uses -> on the returned pointer to access the topper's details — no copying needed.
ex8_topper.c
C
#include <stdio.h>

typedef struct {
    char  name[20];
    int   roll;
    float marks;
} Student;

/* Returns POINTER to the student with highest marks */
Student *findTopper(Student *arr, int n) {
    Student *top = &arr[0];   /* start: assume first is best */
    int i;
    for (i = 1; i < n; i++) {
        if (arr[i].marks > top->marks)
            top = &arr[i];  /* point to new best */
    }
    return top;
}

int main() {
    Student s[4] = {
        {"Ananta", 101, 87.5},
        {"Priya",  102, 92.0},
        {"Rahul",  103, 65.5},
        {"Sneha",  104, 95.0}
    };

    Student *top = findTopper(s, 4);

    printf("Topper : %s\n",   top->name);
    printf("Roll   : %d\n",   top->roll);
    printf("Marks  : %.1f\n", top->marks);
    return 0;
}
output
Topper : Sneha
Roll   : 104
Marks  : 95.0
start at index 1 because index 0 is already set as the starting best. The loop checks students 1, 2, 3 only — no need to compare student 0 against itself.
example 9
9
Update All Records — Pointer Loop Through Array
Function walks array with pointer — updates each struct in place
Update all
The addBonus() function receives a pointer to the array and walks through each element using pointer increment p++. Since it has the actual addresses, every update is permanent — the marks in main change for real.
ex9_update_all.c
C
#include <stdio.h>

typedef struct {
    char  name[20];
    float marks;
} Student;

void addBonus(Student *p, int n, float bonus) {
    int i;
    for (i = 0; i < n; i++) {
        p->marks += bonus;  /* update through pointer */
        p++;                /* move to next struct */
    }
}

void showAll(Student *p, int n) {
    int i;
    for (i = 0; i < n; i++)
        printf("%-10s %.1f\n", p[i].name, p[i].marks);
}

int main() {
    Student s[3] = {
        {"Ananta", 75.0},
        {"Rahul",  58.0},
        {"Priya",  88.0}
    };

    printf("Before bonus:\n"); showAll(s, 3);
    addBonus(s, 3, 5.0);
    printf("After  bonus:\n"); showAll(s, 3);
    return 0;
}
output
Before bonus:
Ananta     75.0
Rahul      58.0
Priya      88.0
After  bonus:
Ananta     80.0
Rahul      63.0
Priya      93.0
example 10 — mini project
🎓
Mini Project — Student Record System
5 functions · 5 students · input · display · grade · topper · average
A complete small program using everything from examples 1–9. Five functions, each doing one job. User enters 5 student records — the program displays them all with grades, finds the topper, and calculates the class average. This is the right way to build a real C program.
ex10_student_system.c
C
#include <stdio.h>
#include <string.h>
#define N 5

typedef struct {
    char  name[20];
    int   roll;
    float marks;
} Student;

/* 1. Read one student from keyboard */
void readStudent(Student *p, int num) {
    printf("Student %d name : ", num);
    scanf("%s", p->name);
    printf("Roll number    : ");
    scanf("%d", &p->roll);
    printf("Marks (out 100): ");
    scanf("%f", &p->marks);
}

/* 2. Get grade from marks */
char getGrade(Student s) {
    if      (s.marks >= 90) return 'A';
    else if (s.marks >= 75) return 'B';
    else if (s.marks >= 55) return 'C';
    else                     return 'F';
}

/* 3. Display all students */
void displayAll(Student *arr, int n) {
    int i;
    printf("\n%-12s %-6s %-8s %s\n",
           "Name","Roll","Marks","Grade");
    printf("--------------------------------\n");
    for (i = 0; i < n; i++) {
        printf("%-12s %-6d %-8.1f %c\n",
               arr[i].name, arr[i].roll,
               arr[i].marks, getGrade(arr[i]));
    }
}

/* 4. Find and return pointer to topper */
Student *findTopper(Student *arr, int n) {
    Student *top = arr;
    int i;
    for (i = 1; i < n; i++)
        if (arr[i].marks > top->marks)
            top = &arr[i];
    return top;
}

/* 5. Compute class average */
float classAverage(Student *arr, int n) {
    float total = 0;
    int i;
    for (i = 0; i < n; i++) total += arr[i].marks;
    return total / n;
}

/* ── main: input → display → topper → average ── */
int main() {
    Student students[N];
    Student *top;
    int i;

    printf("=== Student Record System ===\n\n");

    for (i = 0; i < N; i++) {
        readStudent(&students[i], i + 1);
        printf("\n");
    }

    displayAll(students, N);

    top = findTopper(students, N);
    printf("\nTopper    : %s (%.1f)\n",
           top->name, top->marks);
    printf("Class Avg : %.1f\n",
           classAverage(students, N));

    return 0;
}
sample output
=== Student Record System ===

Student 1 name : Ananta
Roll number    : 101
Marks (out 100): 87.5

Student 2 name : Priya
Roll number    : 102
Marks (out 100): 92.0

... (3 more students)

Name         Roll   Marks    Grade
--------------------------------
Ananta       101    87.5     B
Priya        102    92.0     A
Rahul        103    65.5     C
Sneha        104    95.0     A
Vikram       105    78.0     B

Topper    : Sneha (95.0)
Class Avg : 83.6
Five functions, each does exactly one job: readStudent() — input. getGrade() — grade. displayAll() — output. findTopper() — search. classAverage() — math. main() just calls them in order. This is the correct way to write real C programs.
checklist
  • Ex 1 — Pass struct by value: function(Student s) gets a copy — original unchanged
  • Ex 2 — Function can return a whole struct: Student create(...) { return s; }
  • Ex 3 — Student *p = &s makes p point to s — use p->field to access members
  • Ex 4 — Pass &s to function — inside use p->marks to change the original
  • Ex 5 — dot (.) for direct variable · arrow (->) for pointer to struct
  • Ex 6 — Array of structs passed as Student *arr — access with arr[i].field
  • Ex 7 — Function takes struct, returns char — getGrade(students[i]) in printf
  • Ex 8 — findTopper returns Student* — caller uses top->name to access result
  • Ex 9 — p++ moves pointer to next struct in array — updates each in place
  • Ex 10 — Mini project: 5 functions, 5 students, each function does one job
HTMLEOF echo "Written: $(wc -c < /home/claude/lesson-structures-functions-pointers.html) bytes" cp /home/claude/lesson-structures-functions-pointers.html /mnt/user-data/outputs/lesson-structures-functions-pointers.html echo "Done" Output Written: 47260 bytes Done Done Like Dynamic memory lesson same css html style above create lesson on linked list complete c programming guide with examples Jun 18 Build the complete linked list lesson matching the dynamic memory lesson style Build the complete linked list lesson matching the dynamic memory lesson style Build the complete linked list lesson matching the dynamic memory lesson style bash cat > /home/claude/linked_list_lesson.html << 'HTMLEOF' Linked Lists in C — Complete Guide | Ananta Code Academy
Linked Lists in C — Complete Guide
0%
C Programming  ·  Linked Lists

Linked Lists in C
Complete Guide

Arrays have a fixed size. Linked lists grow and shrink as your program runs — each piece of data points to the next. Built using structs, pointers, and malloc — everything you already know.

What is a node
Create a list
Insert at front
Insert at end
Delete a node
Search
Traverse
7 programs
§1

What is a Linked List? — The Big Idea

Concept

An array stores all its elements in one continuous block of memory, side by side. To grow it, you need realloc() and the whole block might need to move. A linked list solves this differently — each element lives anywhere in memory, and carries the address of the next element along with it.

Think of it like a treasure hunt. Each clue (node) tells you what's at this spot AND gives you directions to the next clue. You don't need to know where all the clues are in advance — you just follow the chain one step at a time.

A linked list of 3 nodes — data + pointer to next
head
10
next →
20
next →
30
next
NULL

Each box above is called a node. Every node has two parts:

  • data — the actual value you want to store (10, 20, 30...)
  • next — a pointer holding the address of the next node (or NULL if it's the last one)

The head is a pointer that always points to the first node — it's your only entry point into the whole list. Lose the head, and you lose access to everything.

FeatureArrayLinked List
SizeFixed at creation (or realloc)Grows/shrinks freely, one node at a time
MemoryOne continuous blockScattered anywhere on the heap
Access element iInstant — arr[i]Must walk from head, node by node
Insert at frontShift everything right — slowJust relink — fast
Extra memoryNoneEach node needs space for the next pointer
the node struct
§2

Defining a Node — struct + Self Pointer

Syntax

A node is just a struct with two fields: the data, and a pointer to another node of the same type. This is called a self-referential structure — the struct contains a pointer to itself.

Syntax — defining a node
struct Node {
    int data;             /* the value stored in this node */
    struct Node *next;    /* pointer to the NEXT node */
};

/* typedef for convenience */
typedef struct Node {
    int  data;
    struct Node *next;
} Node;
How nodes live on the heap — each created with malloc
📦 head (in main)just a pointer — holds address 5000
🏗️ Node @ 5000 — data:10, next:6200created with malloc(sizeof(Node))
🏗️ Node @ 6200 — data:20, next:7400created with malloc(sizeof(Node))
🏗️ Node @ 7400 — data:30, next:NULLlast node — next is NULL
Why "struct Node *next" inside struct Node? You might think this is circular, but it's fine — next is just a pointer (an address), not a full copy of another Node. A pointer's size never depends on what it points to, so there's no infinite size problem.
creating a node
§3

Creating a Node with malloc()

First program

Every new node must be created on the heap using malloc(), because it needs to outlive the function that creates it. A normal stack variable would disappear once the function returns — but a linked list must survive across the whole program.

create_node.c
C
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int  data;
    struct Node *next;
} Node;

int main() {
    /* Create ONE node on the heap */
    Node *first = (Node *) malloc(sizeof(Node));

    if (first == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    first->data = 10;     /* set the data */
    first->next = NULL;    /* this is the only node — points to nothing */

    printf("Node created!\n");
    printf("Data : %d\n", first->data);
    printf("Next : %s\n", first->next == NULL ? "NULL" : "points to something");

    free(first);   /* release when done */
    return 0;
}
output
Node created!
Data : 10
Next : NULL
Always check malloc for NULL before using the pointer — exactly the same rule from the Dynamic Memory lesson. A linked list is built entirely from individually malloc'd nodes, so this check matters even more here.
linking nodes together
§4

Linking Multiple Nodes Manually

Building the chain

To build a list of 3 nodes by hand: create each node, then set each node's next to point at the next one. The very last node's next must be NULL — this marks the end of the list.

link_nodes.c
C
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int  data;
    struct Node *next;
} Node;

int main() {
    /* Create three separate nodes */
    Node *head   = (Node *) malloc(sizeof(Node));
    Node *second = (Node *) malloc(sizeof(Node));
    Node *third  = (Node *) malloc(sizeof(Node));

    /* Fill in data */
    head->data   = 10;
    second->data = 20;
    third->data  = 30;

    /* LINK them together */
    head->next   = second;  /* head points to second */
    second->next = third;   /* second points to third */
    third->next  = NULL;    /* third is the LAST node */

    /* Walk the chain and print */
    Node *temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;   /* move to next node */
    }
    printf("NULL\n");

    return 0;
}
output
10 -> 20 -> 30 -> NULL
temp = temp->next is the single most important line in linked lists. It moves a pointer from the current node to the next node. Every traverse, search, insert, and delete operation uses this exact line to walk through the list.
insert at front
§5

Insert a Node at the Front

Most common operation

To add a new node at the very beginning: create the node, point its next to the current head, then move head to the new node. Order matters — if you move head first, you lose the rest of the list forever.

insertFront(20) — when list is [10 → NULL]
Step 1
malloc a new node, set data = 20
Step 2
newNode->next = head
new node now points to old first node (10)
Step 3
head = newNode
head now points to the new node (20)
Result
List is now: 20 → 10 → NULL
insert_front.c
C
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int  data;
    struct Node *next;
} Node;

/* Inserts at front — note head is passed as POINTER TO POINTER
   because the function needs to CHANGE head itself */
void insertFront(Node **head, int value) {
    Node *newNode = (Node *) malloc(sizeof(Node));
    newNode->data = value;
    newNode->next = *head;   /* point to old first node */
    *head = newNode;         /* head now points to new node */
}

void printList(Node *head) {
    Node *temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

int main() {
    Node *head = NULL;   /* empty list to start */

    insertFront(&head, 10);
    printf("After inserting 10: "); printList(head);

    insertFront(&head, 20);
    printf("After inserting 20: "); printList(head);

    insertFront(&head, 30);
    printf("After inserting 30: "); printList(head);

    return 0;
}
output
After inserting 10: 10 -> NULL
After inserting 20: 20 -> 10 -> NULL
After inserting 30: 30 -> 20 -> 10 -> NULL
Why Node **head — two stars? head is a pointer. To let the function change WHAT head points to (not just the data inside the node), we need a pointer to the pointer. Same double-pointer idea from the Pointers lesson — *head = newNode updates the actual head variable back in main.
insert at end
§6

Insert a Node at the End

Walk to the last node

To add at the end, you must first walk to the last node (the one whose next is NULL), then attach the new node there. Two special cases: an empty list, and a list with existing nodes.

insert_end.c
C
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int  data;
    struct Node *next;
} Node;

void insertEnd(Node **head, int value) {
    Node *newNode = (Node *) malloc(sizeof(Node));
    newNode->data = value;
    newNode->next = NULL;   /* new node is always the new last */

    if (*head == NULL) {     /* CASE 1: empty list */
        *head = newNode;
        return;
    }

    /* CASE 2: walk to the last node */
    Node *temp = *head;
    while (temp->next != NULL) {
        temp = temp->next;
    }
    temp->next = newNode;   /* attach new node at the end */
}

void printList(Node *head) {
    while (head != NULL) {
        printf("%d -> ", head->data);
        head = head->next;
    }
    printf("NULL\n");
}

int main() {
    Node *head = NULL;

    insertEnd(&head, 10);
    insertEnd(&head, 20);
    insertEnd(&head, 30);

    printf("List: "); printList(head);
    return 0;
}
output
List: 10 -> 20 -> 30 -> NULL
Insert at front is O(1) — instant. Insert at end is O(n) — must walk the whole list first. This is one of the most important trade-offs in linked lists: fast at front, slower at end (unless you keep a separate tail pointer, an advanced optimisation).
search
§7

Search for a Value

Traverse and compare

Unlike an array, there is no index to jump to directly. You must walk from the head, checking each node's data until you find a match or reach NULL.

search.c
C
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int  data;
    struct Node *next;
} Node;

/* Returns 1 if found, 0 if not. Also prints position. */
int search(Node *head, int key) {
    Node *temp = head;
    int   pos = 0;

    while (temp != NULL) {
        if (temp->data == key) {
            printf("Found %d at position %d\n", key, pos);
            return 1;
        }
        temp = temp->next;
        pos++;
    }
    printf("%d not found in list\n", key);
    return 0;
}

void insertEnd(Node **head, int value) {
    Node *newNode = (Node *) malloc(sizeof(Node));
    newNode->data = value; newNode->next = NULL;
    if (*head == NULL) { *head = newNode; return; }
    Node *temp = *head;
    while (temp->next != NULL) temp = temp->next;
    temp->next = newNode;
}

int main() {
    Node *head = NULL;
    insertEnd(&head, 10);
    insertEnd(&head, 20);
    insertEnd(&head, 30);
    insertEnd(&head, 40);

    search(head, 30);
    search(head, 99);
    return 0;
}
output
Found 30 at position 2
99 not found in list
delete a node
§8

Delete a Node by Value

Most tricky operation

To delete a node, you must reconnect the chain before freeing it — otherwise you lose access to everything after it. You need a pointer to the node before the one you're deleting, so you can skip over it.

Deleting node with value 20 from 10 → 20 → 30 → NULL
Step 1
Walk and find prev=10, curr=20 (the one to delete)
Step 2
prev->next = curr->next
10's next now points to 30 — 20 is skipped
Step 3
free(curr)
memory for node 20 released
Result
List is now: 10 → 30 → NULL
delete_node.c
C
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int  data;
    struct Node *next;
} Node;

void deleteValue(Node **head, int key) {
    Node *curr = *head;
    Node *prev = NULL;

    /* Special case: deleting the head itself */
    if (curr != NULL && curr->data == key) {
        *head = curr->next;   /* move head forward */
        free(curr);
        return;
    }

    /* Walk, keeping track of prev */
    while (curr != NULL && curr->data != key) {
        prev = curr;
        curr = curr->next;
    }

    if (curr == NULL) {  /* value not found */
        printf("%d not found.\n", key);
        return;
    }

    prev->next = curr->next;  /* skip over curr */
    free(curr);                /* release memory */
}

void insertEnd(Node **head, int value) {
    Node *n = (Node *) malloc(sizeof(Node));
    n->data = value; n->next = NULL;
    if (*head == NULL) { *head = n; return; }
    Node *t = *head;
    while (t->next != NULL) t = t->next;
    t->next = n;
}

void printList(Node *head) {
    while (head != NULL) { printf("%d -> ", head->data); head = head->next; }
    printf("NULL\n");
}

int main() {
    Node *head = NULL;
    insertEnd(&head, 10);
    insertEnd(&head, 20);
    insertEnd(&head, 30);

    printf("Before: "); printList(head);
    deleteValue(&head, 20);
    printf("After : "); printList(head);

    return 0;
}
output
Before: 10 -> 20 -> 30 -> NULL
After : 10 -> 30 -> NULL
Always free() the deleted node. If you only do prev->next = curr->next without calling free(curr), the node is unreachable but its memory is never returned to the OS — a classic linked-list memory leak.
complete reference program
§9

Complete Program — All Operations Together

Reference program
linked_list_complete.c
C
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int  data;
    struct Node *next;
} Node;

void insertFront(Node **head, int v) {
    Node *n = (Node *) malloc(sizeof(Node));
    n->data = v; n->next = *head; *head = n;
}

void insertEnd(Node **head, int v) {
    Node *n = (Node *) malloc(sizeof(Node));
    n->data = v; n->next = NULL;
    if (*head == NULL) { *head = n; return; }
    Node *t = *head;
    while (t->next) t = t->next;
    t->next = n;
}

int count(Node *head) {
    int c = 0;
    while (head) { c++; head = head->next; }
    return c;
}

void printList(Node *head) {
    while (head) { printf("%d -> ", head->data); head = head->next; }
    printf("NULL\n");
}

/* Free EVERY node — call before program ends */
void freeList(Node *head) {
    Node *temp;
    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }
}

int main() {
    Node *head = NULL;

    insertEnd(&head, 10);
    insertEnd(&head, 20);
    insertEnd(&head, 30);
    insertFront(&head, 5);

    printf("List   : "); printList(head);
    printf("Length : %d\n", count(head));

    freeList(head);   /* release ALL nodes — no leaks */
    printf("All memory freed.\n");

    return 0;
}
output
List   : 5 -> 10 -> 20 -> 30 -> NULL
Length : 4
All memory freed.
freeList() is essential before the program ends. Each node was malloc'd individually — they must each be free'd individually too. We save the next pointer in temp BEFORE freeing, because once a node is freed you cannot safely read its next field anymore.
quick reference
§10

Quick Reference — All Operations

OperationSpeedKey line
Create nodeO(1)malloc(sizeof(Node))
Insert at frontO(1)newNode->next = *head; *head = newNode;
Insert at endO(n)Walk to last node, then temp->next = newNode;
SearchO(n)while (temp != NULL) { if (temp->data == key) ... }
DeleteO(n)prev->next = curr->next; free(curr);
Traverse / printO(n)temp = temp->next; until NULL
Free entire listO(n)Save next, free current, repeat
RuleWhy
Always check malloc for NULLOut of memory crashes if you don't
Last node's next is always NULLMarks the end — loops stop here
Use Node** to change head inside a functionSo changes persist back in main
Save next before free(node)You cannot read freed memory safely
Always freeList() before program endsEvery malloc must be matched with free
quiz
Q

Quick Quiz

Q 1 of 5

What two fields does every linked list node have?

Q 2 of 5

Why must every node be created with malloc() instead of a normal variable?

Q 3 of 5

Why does insertFront take Node **head instead of Node *head?

Q 4 of 5

Why is insert at front O(1) but insert at end O(n)?

Q 5 of 5

When deleting a node, why save temp = head BEFORE calling free(temp) in freeList()?

Lesson Checklist

  • A linked list node is a struct containing data and a pointer to the next node
  • head is a pointer to the first node — losing it loses the whole list
  • Every node is created with malloc(sizeof(Node)) — never a stack variable
  • The last node's next is always NULL — this marks the end
  • temp = temp->next is how you walk through a linked list, one node at a time
  • Insert at front: newNode->next = *head; *head = newNode; — O(1) speed
  • Insert at end requires walking to the last node first — O(n) speed
  • Delete: relink prev->next = curr->next BEFORE calling free(curr)
  • Node **head is needed whenever a function must change the head itself
  • I completed the quiz