Data Structures, Algorithms, and Recursion
From the CS50: Introduction to Computer Science curriculum
TL;DR
Data structures are ways to organize data, algorithms are step-by-step instructions to solve problems, and recursion is a technique where a function calls itself. Together, they form the foundation for efficient and effective programming. Understanding them helps you write better, faster code that uses less memory.
1. The Mental Model
Think of data structures as different types of containers for your stuff, like a neatly organized shelf versus a messy drawer. Algorithms are the recipes you follow to do things with that stuff, and recursion is like a recipe step that says, "do this whole recipe again on a smaller part."
2. The Core Material
Data Structures: Organizing Your Data

Photo by Brett Sayles on Pexels
Data structures are specific ways to arrange data in a computer's memory so that it can be accessed and manipulated efficiently. Different problems benefit from different structures.
- Arrays: A collection of items stored at contiguous memory locations. Great for quick access if you know the index.
c int numbers[5] = {10, 20, 30, 40, 50}; // An array of 5 integers printf("%d\n", numbers[2]); // Accesses the third element (30) - Linked Lists: A sequence of data elements, where each element points to the next. Good for dynamic size and efficient insertions/deletions in the middle.
c // Conceptual idea: // Node1 -> Node2 -> Node3 // Each Node has data and a pointer to the next Node. - Trees: Hierarchical structures where each node can have zero or more child nodes. Useful for representing hierarchies (like file systems) or for efficient searching (Binary Search Trees).
- Hash Tables: A data structure that maps keys to values for very fast lookups. Think of it like a dictionary where you can quickly find a definition using a word.
Algorithms: Step-by-Step Solutions

Photo by Kanhaiya Sharma on Pexels
An algorithm is a finite sequence of well-defined, computer-implementable instructions, typically to solve a class of problems or to perform a computation.
- Searching Algorithms:
- Linear Search: Checks each element one by one until a match is found. Simple but slow for large datasets.
- Binary Search: Efficiently finds an item in a sorted list by repeatedly dividing the search interval in half. Much faster than linear search.
- Sorting Algorithms:
- Bubble Sort: Repeatedly steps through the list, compares adjacent elements, and swaps them if they are in the wrong order. Simple but inefficient for large lists.
- Merge Sort: Divides the unsorted list into n sublists, each containing one element, then repeatedly merges sublists to produce new sorted sublists until there is only one sorted list remaining. Efficient and stable.
Recursion: Functions Calling Themselves

Photo by Steve A Johnson on Pexels
Recursion is a powerful programming technique where a function solves a problem by calling itself one or more times with smaller inputs until it reaches a base case that can be solved directly.
- Base Case: The condition under which the function stops calling itself. Without it, you get an infinite loop (stack overflow!).
- Recursive Step: The part where the function calls itself with a modified (usually smaller) input.
Let's look at how recursion works to calculate factorial (e.g., 5! = 5 * 4 * 3 * 2 * 1):
int factorial(int n) {
// Base Case: If n is 0 or 1, factorial is 1
if (n <= 1) {
return 1;
}
// Recursive Step: n * factorial of (n-1)
else {
return n * factorial(n - 1);
}
}
// How factorial(3) unfolds:
// factorial(3) returns 3 * factorial(2)
// factorial(2) returns 2 * factorial(1)
// factorial(1) returns 1 (Base Case)
// factorial(2) returns 2 * 1 = 2
// factorial(3) returns 3 * 2 = 6
Here's a diagram illustrating the conceptual flow of a recursive function:
graph TD
A["Call Function with N"] --> B{"Is N a Base Case?"}
B -- Yes --> C["Return Base Value"]
B -- No --> D["Perform Operation"]
D --> E["Call Function with Smaller N (Recursive Step)"]
E --> F["Combine result from Smaller N with current operation"]
F --> A
3. Worked Example
Let's combine concepts with a concrete problem: counting the number of nodes in a singly linked list using recursion.
First, let's define a simple Node structure for our linked list:
#include <stdio.h>
#include <stdlib.h>
// Define a Node structure
typedef struct Node {
int data;
struct Node *next;
} Node;
// Function to create a new node
Node *createNode(int data) {
Node *newNode = (Node *)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed!\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// Function to count nodes recursively
int countNodesRecursive(Node *head) {
// Base Case: If the list is empty (head is NULL), there are 0 nodes.
if (head == NULL) {
return 0;
}
// Recursive Step: Add 1 (for the current node) to the count of the rest of the list.
else {
return 1 + countNodesRecursive(head->next);
}
}
int main() {
// Create a linked list: 10 -> 20 -> 30 -> NULL
Node *head = createNode(10);
head->next = createNode(20);
head->next->next = createNode(30);
// Count the nodes using the recursive function
int count = countNodesRecursive(head);
printf("Number of nodes in the list: %d\n", count); // Expected: 3
// Free the allocated memory (important!)
Node *current = head;
while (current != NULL) {
Node *next = current->next;
free(current);
current = next;
}
return 0;
}
In this example:
1. Data Structure: We're using a struct Node to represent a linked list.
2. Algorithm (Recursive): countNodesRecursive is our algorithm.
3. Recursion: The function calls itself (countNodesRecursive(head->next)) with a smaller version of the problem (the rest of the list) until the base case (head == NULL) is met. It then combines the results by adding 1 at each step.
4. Key Takeaways
- Choose the right data structure; it significantly impacts your program's efficiency for specific operations.
- Algorithms are explicit procedures for solving problems, often analyzed for their time and space complexity.
- Recursion simplifies code for problems that can be broken down into smaller, self-similar subproblems.
- Every recursive function needs a clear base case to prevent infinite loops.
- Think about time complexity (how long an algorithm takes) and space complexity (how much memory it uses) when choosing algorithms and data structures.
- Arrays offer fast indexed access but fixed size, while linked lists are dynamic but require traversing.
- Binary search is much faster than linear search, but only works on sorted data.
Common Mistakes to Avoid:
* Forgetting a base case in recursion, leading to a stack overflow.
* Choosing an inefficient data structure for the problem at hand (e.g., using an array for frequent middle insertions/deletions).
* Not considering the sorted status of data when choosing a search algorithm.
* Modifying data in a recursive function without understanding the side effects on previous calls.
5. Now Try It
Write a C function printListRecursive(Node *head) that takes the head of a linked list as input and recursively prints the data of each node in order. Your main function should create a small linked list (e.g., 5 -> 10 -> 15) and call your printListRecursive function.
Success looks like: Your program compiles, runs without errors, and prints 5 10 15 (or whatever data you put in your list) to the console.
Frequently asked about Data Structures, Algorithms, and Recursion
Study this next
Get the full CS50: Introduction to Computer Science curriculum
Clone the complete plan to your dashboard for unlimited AI-generated notes, practice quizzes, and a personalised revision schedule.
Create Free Account