View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All

Data Structures & Algorithm in Python: Everything You Need to Know in 2025

By Rohit Sharma

Updated on Jun 13, 2025 | 30 min read | 45.87K+ views

Share:

Did You Know? As of 2025, Python's default sorting algorithm has transitioned from Timesort to Powersort? Powersort is an adaptive sorting algorithm designed to optimally exploit existing order in the input data with minimal overhead. It offers superior performance and stability, making it a drop-in replacement for Timsort in CPython 3.11 and later versions.

In 2025, data structures & algorithms remain the backbone of efficient programming, particularly in Python. Understanding these concepts is crucial for optimizing code performance and solving complex problems. By understanding data structures such as lists, trees, and graphs, along with algorithms like sorting and searching, you can tackle a wide range of computational challenges. By the end of this blog, you'll grasp essential Python data structures (lists, dictionaries, trees, graphs) and algorithms (sorting, searching, recursion), key for building efficient applications.

This blog will cover essential data structures & algorithms in Python, explain how they work, and demonstrate their real-world applications. You'll also learn how to implement them effectively to improve your programming skills and efficiency.

Want to break into software development? Enroll in our Online Software Development Courses from top universities. Gain hands-on experience with the latest tools, and build skills that lead to a 66% average salary hike.

What Are the Types of Data Structure Algorithms in Python?

You can categorize Python algorithms based on their purpose, design principles, and use cases. These categories, including sorting, searching, dynamic programming, and graph algorithms, are crucial for solving many computational problems. They help improve app performance and address optimization challenges.

Understanding the strengths and limitations of each type helps you choose the right algorithm for specific tasks. This includes speeding up search operations, optimizing data processing, and preparing for coding interviews.  

Explore our top software development programs and gain the skills you need to stand out in your career:

Also read: A Comprehensive Guide to Understanding the Different Types of Data in 2025

Here’s an overview of the common types of algorithms related to data structure in Python.

Algorithm Type

Algorithm

Description

Sorting Algorithms Bubble Sort Compares adjacent elements and swaps them if out of order.
  Merge Sort Divides the data into smaller parts, sorts them, and merges them.
  Quick Sort Uses a "pivot" element to partition data and recursively sort.
  Insertion Sort Builds a sorted list one element at a time by inserting it into the correct position.
Searching Algorithms Linear Search Iterates through each element until the desired one is found.
  Binary Search Divides a sorted list in half repeatedly to find the target.
Graph Algorithms Depth-First Search (DFS) Explores as far down one branch as possible before going back.
  Breadth-First Search (BFS) Explores all nodes at the present depth before moving deeper.
  Dijkstra’s Algorithm Locates the shortest path in a weighted graph.
Dynamic Programming Fibonacci Sequence Solves by breaking the problem into smaller subproblems and storing results to avoid redundant calculations.
  Knapsack Problem Optimizes resource allocation under constraints using dynamic programming.
Greedy Algorithms Activity Selection Chooses the maximum number of non-overlapping activities.
  Huffman Encoding Builds an optimal binary tree for data compression by assigning shorter codes to frequent characters.
Backtracking Algorithms N-Queens Problem Places N queens on a chessboard such that no two threaten each other.
  Sudoku Solver Solves a Sudoku puzzle by testing numbers and backtracking when needed.

Now that you have an idea of the types of algorithms related to data structure in Python, let's discuss them in complete detail.

background

Liverpool John Moores University

MS in Data Science

Dual Credentials

Master's Degree17 Months

Placement Assistance

Certification6 Months

What Are the Common Sorting Algorithms in Python?

Sorting algorithms are fundamental tools in computer science that can arrange data in a specific order, typically ascending or descending. These algorithms can arrange data efficiently, enabling quicker and more reliable access to information.

Here’s a breakdown of the common sorting algorithms in Python.

How Does Merge Sort Work?

Merge Sort is a popular divide-and-conquer sorting algorithm that breaks down a problem into smaller sub-problems, solves them, and then combines the results. It is efficient, stable, and works well for large datasets.

The merge sort follows the divide-and-conquer approach to solve the problem.

  • Divide: Split the unsorted array into two halves until each subarray contains a single element.
  • Conquer: Recursively sort each half.
  • Combine: Merging the two sorted halves into a single sorted array.

Code snippet:

def merge_sort(arr):
    # Base case: A single-element list is already sorted
    if len(arr) <= 1:
        return arr

    # Divide: Find the middle point and split the array
    mid = len(arr) // 2
    left_half = merge_sort(arr[:mid])
    right_half = merge_sort(arr[mid:])

    # Conquer: Merge the sorted halves
    return merge(left_half, right_half)

def merge(left, right):
    sorted_array = []
    i = j = 0

    # Compare elements from both halves and merge
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            sorted_array.append(left[i])
            i += 1
        else:
            sorted_array.append(right[j])
            j += 1

    # Append any remaining elements from the left or right half
    sorted_array.extend(left[i:])
    sorted_array.extend(right[j:])
    
    return sorted_array

# Example usage
unsorted_array = [38, 27, 43, 3, 9, 82, 10]
sorted_array = merge_sort(unsorted_array)
print("Sorted Array:", sorted_array)

Output:

Sorted Array: [3, 9, 10, 27, 38, 43, 82]

Explanation:

The merge_sort function splits the array into two halves recursively and sorts them by merging them back together in a sorted manner. It uses the merge function to compare elements from both halves and create a sorted array. The algorithm has a time complexity of O(n log n), making it efficient for large datasets.

What Is Heap Sort, and How Is It Implemented?

Heap Sort is a comparison-based sorting algorithm that organizes and sorts data by using the properties of a binary heap data structure. It sorts elements by repeatedly removing the largest (or smallest) element from the heap and placing it at the end of the array.

Here’s the binary heap data structure in Python.

  • Max-Heap: Each parent node is greater than or equal to its children.
  • Min-Heap: Each parent node is less than or equal to its children.

Code snippet:

def heapify(arr, n, i):
    """
    Function to maintain the max-heap property.
    :param arr: Array to heapify
    :param n: Size of the heap
    :param i: Index of the current node
    """
    largest = i  # Assume the current node is the largest
    left = 2 * i + 1  # Left child index
    right = 2 * i + 2  # Right child index

    # Check if the left child exists and is greater than the current largest
    if left < n and arr[left] > arr[largest]:
        largest = left

    # Check if the right child exists and is greater than the current largest
    if right < n and arr[right] > arr[largest]:
        largest = right

    # If the largest node is not the current node, swap and continue heapifying
    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]  # Swap
        heapify(arr, n, largest)

def heap_sort(arr):
    """
    Main function to perform Heap Sort.
    :param arr: Array to be sorted
    """
    n = len(arr)

    # Step 1: Build a max-heap
    for i in range(n // 2 - 1, -1, -1):
        heapify(arr, n, i)

    # Step 2: Extract elements from the heap one by one
    for i in range(n - 1, 0, -1):
        arr[0], arr[i] = arr[i], arr[0]  # Move the root to the end
        heapify(arr, i, 0)  # Restore the max-heap property for the reduced heap

# Example usage
unsorted_array = [4, 10, 3, 5, 1]
heap_sort(unsorted_array)
print("Sorted Array:", unsorted_array)

Output:

Sorted Array: [1, 3, 4, 5, 10]

Explanation:

The heap_sort function first builds a max-heap from the input array. It then repeatedly swaps the root (maximum element) of the heap with the last element and reduces the heap size, restoring the heap property each time using the heapify function. This process continues until the array is fully sorted. The time complexity of heap_sort is O(n log n), which is efficient for large datasets.

What Is Radix Sort, and When Should You Use It?

Source: Tutorials Point

Radix Sort is a non-comparative integer sorting algorithm that sorts numbers (or strings) by processing individual digits or characters. It sorts numbers based on their individual digits, starting from the least significant digit (LSD) or the most significant digit (MSD) and repeatedly grouping numbers with the same digit.

Here are the steps followed in Radix Sort.

  • Find the largest number: The algorithm locates the largest number in the dataset to determine the number of digits (or place values) to process.
  • Sort by each digit: Start from the least significant digit (LSD) and sort the numbers into buckets. Then, reassemble the numbers.
  • Repeat for each digit: Move to the next digit, repeat the process, and reassemble the numbers until the most significant digit is processed.

Also read: 16+ Essential Python String Methods You Should Know (With Examples)

Code snippet: 

def counting_sort(arr, exp):
    """
    A helper function to perform counting sort based on a digit represented by exp (10^i).
    :param arr: The array to be sorted
    :param exp: The digit place (1s, 10s, 100s, etc.)
    """
    n = len(arr)
    output = [0] * n  # Output array that will store sorted numbers
    count = [0] * 10  # Count array to store the frequency of digits (0-9)

    # Count occurrences of each digit in the current place
    for i in range(n):
        index = arr[i] // exp
        count[index % 10] += 1

    # Change count[i] to be the actual position of this digit in the output
    for i in range(1, 10):
        count[i] += count[i - 1]

    # Build the output array, ensuring stable sorting
    for i in range(n - 1, -1, -1):
        index = arr[i] // exp
        output[count[index % 10] - 1] = arr[i]
        count[index % 10] -= 1

    # Copy the sorted elements back into the original array
    for i in range(n):
        arr[i] = output[i]

def radix_sort(arr):
    """
    The main function to implement Radix Sort.
    :param arr: The array to be sorted
    """
    # Find the maximum number to determine the number of digits
    max_num = max(arr)

    # Perform counting sort for every digit (1s, 10s, 100s, etc.)
    exp = 1  # Start from the least significant digit (1s)
    while max_num // exp > 0:
        counting_sort(arr, exp)
        exp *= 10  # Move to the next digit

# Example usage
unsorted_array = [170, 45, 75, 90, 802, 24, 2, 66]
radix_sort(unsorted_array)
print("Sorted Array:", unsorted_array)

Output:

Sorted Array: [2, 24, 45, 66, 75, 90, 170, 802]

Explanation:

The radix_sort function sorts an array of integers by processing each digit, starting from the least significant digit (1s place). It uses the counting_sort helper function to perform a stable sort for each digit place. The counting sort works by counting the occurrences of each digit and positioning the elements accordingly. The process repeats for each digit (1s, 10s, 100s, etc.) until the entire array is sorted. 

Become a Full Stack Development specialist with AI integration through the AI-Powered Full Stack Development Course by IIITB. Gain hands-on experience in web applications, master frontend and backend technologies, and learn AI integration to become a 10x developer. Apply now to elevate your skills!

Also Read: Selection Sort Algorithm in Data Structure: Code, Function & Example

After discovering the working of sorting algorithms in Python, let’s explore searching algorithms.

How Do Searching Algorithms Work in Python?

Searching algorithms can locate specific elements within a collection of data. Python provides several efficient searching methods to retrieve information from various data structures & algorithms. Here’s a quick look at the different searching algorithms in Python.

What Is Linear Search, and How Does It Work?

Source: Medium

Linear Search checks every element of a list (or array) in sequence until the desired element is found or the list is completely searched. 

Here’s how Linear Search works.

  • Begin with the first element in the list.
  • Compare the current element with the target element.
  • If the element is found, return its position; otherwise, move to the next element.
  • Repeat until the element is found or the end of the list is reached.

Code snippet:

def linear_search(arr, target):
    """
    Function to perform linear search on a list.
    :param arr: List to be searched
    :param target: The element to search for
    :return: Index of the target element if found, otherwise -1
    """
    for i in range(len(arr)):
        if arr[i] == target:  # Check if the current element matches the target
            return i  # Return the index if element is found
    return -1  # Return -1 if target is not found

# Example usage
arr = [10, 20, 30, 40, 50]
target = 30
result = linear_search(arr, target)

if result != -1:
    print(f"Element {target} found at index {result}")
else:
    print(f"Element {target} not found")

Output:

Element 30 found at index 2

Explanation:

The linear_search function scans through the list element by element. It compares each element with the target value. If a match is found, it returns the index of the element. If the target is not found after scanning all elements, it returns -1. The time complexity of this search is O(n), where n is the number of elements in the list.

Also read: Introduction to Linear Search Algorithm: Time Complexity and Examples for 2025

How Does Binary Search Optimize Searching?

Source: Research Gate

Binary Search finds the position of a target value within a sorted array or list by dividing the search interval in half. It is a classic example of the divide-and-conquer strategy.

Here’s how Binary Search works.

  • Split the array into two halves by finding the middle element.
  • If the target element is smaller than the middle element, search the left half; otherwise, search the right half.
  • The array is divided into smaller subproblems until the target is located or the search space is exhausted.

Code snippet:

def binary_search(arr, target):
    """
    Function to perform binary search on a sorted array.
    :param arr: Sorted list to be searched
    :param target: The element to search for
    :return: Index of the target element if found, otherwise -1
    """
    low = 0
    high = len(arr) - 1

    while low <= high:
        mid = (low + high) // 2  # Find the middle index
        
        # Check if the target is at the middle
        if arr[mid] == target:
            return mid
        
        # If the target is smaller, ignore the right half
        elif arr[mid] > target:
            high = mid - 1
        
        # If the target is larger, ignore the left half
        else:
            low = mid + 1
    
    # If the target is not found
    return -1

# Example usage
arr = [1, 3, 5, 7, 9, 11, 13]
target = 7
result = binary_search(arr, target)

if result != -1:
    print(f"Element {target} found at index {result}")
else:
    print(f"Element {target} not found")

Output:

Element 7 found at index 3

Explanation:

The binary_search function efficiently searches for a target element in a sorted array by repeatedly dividing the search range in half. It compares the target with the middle element. Based on the comparison, it narrows the search to either the left or right half. This continues until the target is found or the range is empty. This results in a time complexity of O(log n).

Enhance your data analysis skills with the free Case Study using Tableau, Python, and SQL. Analyze real-world business challenges, gain hands-on experience with churn analysis, and create compelling visualizations - perfect for analysts and aspiring data scientists.

You can check the following section for the graph algorithms in Python.

Also read: Difference Between Linear Search and Binary Search: Efficiency and Applications

What Are the Key Graph Algorithms in Python?

Graph algorithms can solve network-related problems in Python. Networks, represented as graphs, consist of nodes (vertices) and edges (connections between nodes). You can apply various graph algorithms to solve a wide range of network-related tasks efficiently.

Source: BogoToBogo

Here’s a quick look at the different graph algorithms in Python.

How Does Depth-First Search (DFS) Traverse Graphs?

Depth-First Search (DFS) traverses as far as possible along a branch of the graph before backtracking. It starts at a given node and moves as far as possible down each branch before moving to the next one. The recursive approach is the most popular for DFS implementations.

Here’s how recursive DFS works.

  • Begin the traversal at the starting node (could be any node in the graph).
  • Mark the current node as visited so as not to visit again.
  • Recursively visit all the unvisited neighbors of the current node.
  • Once all neighbors are visited, backtrack to the previous node.

Code snippet:

def dfs(graph, node, visited=None):
    """
    Perform Depth-First Search (DFS) on the graph using recursion.
    :param graph: The graph represented as an adjacency list (dict of lists)
    :param node: The current node being explored
    :param visited: Set of visited nodes to avoid revisiting
    """
    if visited is None:
        visited = set()  # Initialize the visited set if it's not provided

    # Mark the node as visited
    visited.add(node)
    print(node, end=' ')  # Print the current node

    # Recursively visit all unvisited neighbors of the current node
    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

# Example usage:
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

# Perform DFS starting from node 'A'
dfs(graph, 'A')

Output:

A B D E F C 

Explanation:

The dfs function performs a Depth-First Search on a graph using recursion. It starts from a specified node, marks it as visited, and then recursively visits all its unvisited neighbors. Each visited node is printed as it is explored. The function uses a visited set to avoid revisiting nodes and infinite loops. The graph is represented as an adjacency list (a dictionary of lists). In this example, starting from node 'A', the DFS explores nodes A, B, D, E, F, and C in that order.

Advance your career with the Master’s in Artificial Intelligence and Machine Learning from IIITB and LJMU. Gain hands-on experience with 60+ real-world case studies, 80+ programming tools, and earn a dual-accredited degree. Join over 3,500 successful graduates and apply today!

Also read: DFS Algorithm Explained: Simple Guide with Examples

What Is Dijkstra’s Algorithm and Its Applications?

Source: Medium

Dijkstra algorithm locates the shortest path between nodes in a graph, particularly for weighted graphs where the edges have non-negative weights. It has applications in GPS navigation systems, routing algorithms, and network optimization.

Here’s how Dijikstra’s algorithm works.

  1. Set the distance to the source node as 0 and to all other nodes as infinity. Mark all nodes as unvisited.
  2. Pick the unvisited node with the smallest distance.
  3. Calculate the tentative distance through the selected node. If this tentative distance is shorter than the current shortest distance to the neighbor, update the neighbor's distance.
  4. Mark the selected node as visited.
  5. Repeat steps 2-4 until all nodes are visited or the destination node is reached.

Code snippet:

import heapq

def dijkstra(graph, start):
    """
    Implements Dijkstra's algorithm to find the shortest paths from the start node.
    : param graph: A dictionary where keys are nodes and values are dictionaries of neighbors and edge weights.
    :param start: The starting node for the shortest path calculation.
    :return: A dictionary of the shortest distance from the start node to every other node.
    """
    # Priority queue (min-heap) to select the node with the smallest tentative distance
    pq = [(0, start)]  # (distance, node)
    distances = {node: float('inf') for node in graph}  # Set all distances to infinity initially
    distances[start] = 0
    visited = set()  # To track visited nodes

    while pq:
        current_distance, current_node = heapq.heappop(pq)  # Get the node with the smallest distance

        if current_node in visited:
            continue  # Skip already visited nodes

        visited.add(current_node)

        # Explore the neighbors of the current node
        for neighbor, weight in graph[current_node].items():
            if neighbor in visited:
                continue  # Skip visited neighbors

            distance = current_distance + weight
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(pq, (distance, neighbor))

    return distances

# Example graph (Adjacency List representation)
graph = {
    'A': {'B': 1, 'C': 4},
    'B': {'A': 1, 'C': 2, 'D': 5},
    'C': {'A': 4, 'B': 2, 'D': 1},
    'D': {'B': 5, 'C': 1}
}

# Find the shortest paths from node 'A'
distances = dijkstra(graph, 'A')
print(distances)

Output:

{'A': 0, 'B': 1, 'C': 3, 'D': 4}

Explanation:

The dijkstra function implements Dijkstra's algorithm to find the shortest path from a starting node to all other nodes in a graph. It uses a priority queue (min-heap) to always select the node with the smallest tentative distance, updating distances as it explores the graph. The result is a dictionary containing the shortest distance from the start node to each other node.

Also read: Exploring Dijkstra’s Algorithm: Step-by-Step Guide to Finding the Shortest Path

Now that you have an idea about the different algorithms in Python, check out how dynamic programming can simplify problem-solving.

How Does Dynamic Programming Simplify Complex Problems?

Dynamic programming solves simpler subproblems and stores the results to avoid redundant computations. It is suitable for optimization problems. 

Here are some examples of dynamic programming in Python.

What Are Examples of Dynamic Programming Problems?

Check the following examples of dynamic programming.

1. Fibonacci Sequence

The Fibonacci sequence calculates the nth Fibonacci number. Each term in a Fibonacci sequence is the sum of the two preceding ones, starting from 0 and 1. Dynamic programming can optimize the calculation of the Fibonacci sequence by storing already computed Fibonacci numbers and turning them into a linear time solution.

Code snippet:

def fibonacci(n):
    # Base case
    if n == 0: return 0
    if n == 1: return 1
    
    # Create a table to store results of subproblems
    fib = [0] * (n + 1)
    fib[0], fib[1] = 0, 1
    
    # Fill the table from 2 to n
    for i in range(2, n + 1):
        fib[i] = fib[i-1] + fib[i-2]
    
    return fib[n]

# Example usage:
print(fibonacci(10))  # Output: 55

Output:

55

Explanation:

The fibonacci function calculates the nth Fibonacci number using dynamic programming. It starts by handling the base cases for n = 0 and n = 1. Then, it creates a table (a list) to store the Fibonacci numbers from 0 to n. The function iterates from 2 to n, filling in the table by adding the previous two Fibonacci numbers. Finally, it returns the nth Fibonacci number. 

2. Knapsack Problem

Given items with weights and values, the Knapsack problem involves selecting the maximum value that can be obtained by selecting items within a weight limit. Using dynamic programming, you can maximize the total value while considering the constraint.

Code snippet:

def knapsack(weights, values, capacity):
    n = len(weights)
    # Create a DP table to store maximum value at each weight
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]
    
    for i in range(1, n + 1):
        for w in range(capacity + 1):
            if weights[i-1] <= w:
                dp[i][w] = max(dp[i-1][w], dp[i-1][w-weights[i-1]] + values[i-1])
            else:
                dp[i][w] = dp[i-1][w]
    
    return dp[n][capacity]

# Example usage:
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
capacity = 5
print(knapsack(weights, values, capacity))  # Output: 7

Output:

7

Explanation:

The knapsack function solves the 0/1 knapsack problem using dynamic programming. It builds a table where each entry represents the maximum value achievable for a given capacity and set of items, and iterates through the items to decide whether to include them in the knapsack based on their weight and value. The result is the maximum value that can fit in the given capacity.

Future-proof your tech career with upGrad’s AI-Driven Full-Stack Development Bootcamp. Earn certifications from Microsoft, upGrad, and NSDC while mastering AI-powered development. Build real projects, solve 150+ coding challenges, and get expert mentorship. Secure your spot today!

Also read: What is a Greedy Algorithm: Types and Code Examples

3. Longest Common Subsequence (LCS)

The Longest Common Sequence (LCS) problem finds the longest character sequence that appears in the same order in two strings. In this case, dynamic programming avoids recalculating solutions for overlapping subproblems.

Code snippet:

def lcs(X, Y):
    m, n = len(X), len(Y)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if X[i-1] == Y[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    
    return dp[m][n]

# Example usage:
X = "ABCBDAB"
Y = "BDCAB"
print(lcs(X, Y))  # Output: 4

Output:

4

Explanation:

The LCS function finds the length of the longest common subsequence (LCS) between two strings using dynamic programming. It builds a table to store the lengths of common subsequences and iteratively fills the table by comparing characters from both strings. The final value in the table represents the length of the LCS.

4. Matrix Chain Multiplication

The Matrix Chain Multiplication problem asks for the most efficient way to multiply a chain of matrices together. Dynamic programming breaks the problem into subproblems of multiplying smaller chains of matrices.

Code snippet:

def matrix_chain_order(p):
    n = len(p) - 1
    dp = [[0] * n for _ in range(n)]
    
    # l is the chain length
    for length in range(2, n + 1):
        for i in range(n - length + 1):
            j = i + length - 1
            dp[i][j] = float('inf')
            for k in range(i, j):
                q = dp[i][k] + dp[k+1][j] + p[i]*p[k+1]*p[j+1]
                dp[i][j] = min(dp[i][j], q)
    
    return dp[0][n-1]

# Example usage:
p = [30, 35, 15, 5, 10, 20, 25]
print(matrix_chain_order(p))  # Output: 15125

Output:

15125

Explanation:

The matrix_chain_order function uses dynamic programming to determine the minimum cost of multiplying a chain of matrices. It iterates through possible chain lengths and uses a table (dp) to store the minimum multiplication cost for each sub-chain. The function returns the minimum cost to multiply the entire chain of matrices.

5. Coin Change Problem

The Coin Change problem asks for the minimum number of coins to make a certain amount from a given set of denominations.

Code snippet:

def coin_change(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0  # Base case: 0 coins needed to make 0 amount
    
    for i in range(1, amount + 1):
        for coin in coins:
            if i - coin >= 0:
                dp[i] = min(dp[i], dp[i-coin] + 1)
    
    return dp[amount] if dp[amount] != float('inf') else -1

# Example usage:
coins = [1, 2, 5]
amount = 11
print(coin_change(coins, amount))  # Output: 3 (5 + 5 + 1)

Output:

3

Explanation:

The coin_change function solves the coin change problem using dynamic programming. It iteratively computes the minimum number of coins required to make each amount from 0 to the target amount. The array dp stores the minimum coins for each amount, and the function returns the result for the given amount. If it's not possible to make the amount, it returns -1.

6. Edit Distance (Levenshtein Distance)

The Edit Distance problem calculates the minimum number of operations (insertions, deletions, or substitutions) to convert one string into another. Dynamic programming ensures that overlapping subproblems are solved once and reused.

Code snippet:

def edit_distance(str1, str2):
    m, n = len(str1), len(str2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    for i in range(m + 1):
        for j in range(n + 1):
            if i == 0:
                dp[i][j] = j  # Insert all characters of str2
            elif j == 0:
                dp[i][j] = i  # Delete all characters of str1
            elif str1[i-1] == str2[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])
    
    return dp[m][n]

# Example usage:
print(edit_distance("kitten", "sitting"))  # Output: 3

Output:

3

Explanation:

The edit_distance function computes the minimum number of operations (insertions, deletions, or substitutions) required to convert one string into another. It uses dynamic programming to build a table, where each entry represents the minimum number of operations needed for the corresponding substring pairs.

Also read: Top 50 Python Project Ideas with Source Code in 2025

If you are curious to know about the greedy algorithms in Python, explore the subsequent section.

What Are Greedy Algorithms, and Where Are They Used?

A greedy algorithm in Python makes the optimal choice at each step, hoping to find an optimum solution. It's like making decisions without looking too far ahead, always choosing the immediate best option.

Here’s how the greedy algorithms in Python works.

  • Select the best immediate option.
  • Remove the chosen option and update the remaining problem.
  • Continue until a solution is found.

Greedy algorithms have applications in the following fields.

Source: Plain English

  • Network and Graph Algorithms: Finding the shortest path between two nodes in a weighted graph.  
  • Data Compression: Assigning variable-length codes to characters based on their frequency.  
  • Scheduling: Selecting the maximum number of non-overlapping activities from a set of activities.  
  • Resource Allocation: Determining the optimal selection of items to maximize value.  

Here’s an example of Huffman coding, which is a greedy algorithm.

How Does Huffman Coding Work?

Huffman coding is used for lossless data compression. It identifies the frequency of characters (or symbols) in a dataset and assigns shorter codes to more frequent characters and longer codes to less frequent ones. 

Here’s the working of Huffman coding.

  • Count the frequency of each character (or symbol) in the input data.
  • Create a min-heap where each node contains a symbol and its frequency.  
  • Build a Huffman tree using the frequency of characters. 
  • Traverse the Huffman Tree to assign binary codes to the symbols
  • Replace each symbol in the input data with its corresponding Huffman code.
  • Traverse the Huffman Tree following the bits in the encoded data for decoding.

Curious about how backtracking algorithms can crack constraint problems in Python? Scroll down to discover.

Also read: Data Structures in Python – Complete Guide

How Do Backtracking Algorithms Solve Constraint Problems?

Source: Medium

Backtracking is a recursive approach to solving problems involving constraint satisfaction. The core idea is to explore all possible solutions to a problem, systematically eliminating the infeasible ones and eventually selecting the optimal or valid solution. 

Here are the key characteristics of backtracking algorithms.

  • Recursive Exploration: The algorithm breaks down the problem into smaller subproblems.
  • Pruning: If a partial solution does not meet the problem's constraints, it will not be reconsidered. This removes unnecessary steps.
  • Decision Tree Traversal: Backtracking can be visualized as a decision tree. The algorithm explores each branch systematically before backtracking.
  • Feasible Solution: The algorithm generates potential solutions and checks whether they satisfy the given constraints.

Here’s a brief idea of how backtracking algorithms work.

  • Start from an initial state and try to extend it step by step.
  • Decide at each step, and check if the decision leads to a valid or feasible solution.
  • If the current solution is valid, proceed to the next step.
  • If the current solution violates constraints, backtrack to the previous step and try a different option.
  • Repeat this process until either a solution is found or all possibilities are exhausted.

Here are a few examples of the backtracking algorithms in Python.

What Are Examples of Backtracking Algorithms?

Check the following examples of the backtracking algorithms in Python.

1. N-Queens Problem

The N-Queens problem is to place N queens on an N x N chessboard such that no two queens threaten each other. A queen can attack another queen in the same row, column, or diagonal.

Code snippet:

def is_safe(board, row, col, n):
    # Check column
    for i in range(row):
        if board[i][col] == 1:
            return False
    
    # Check upper-left diagonal
    for i, j in zip(range(row-1, -1, -1), range(col-1, -1, -1)):
        if board[i][j] == 1:
            return False
    
    # Check upper-right diagonal
    for i, j in zip(range(row-1, -1, -1), range(col+1, n)):
        if board[i][j] == 1:
            return False
    
    return True

def solve_n_queens(board, row, n):
    if row >= n:  # All queens are placed
        return True
    
    for col in range(n):
        if is_safe(board, row, col, n):
            board[row][col] = 1  # Place queen
            
            if solve_n_queens(board, row + 1, n):
                return True
            
            board[row][col] = 0  # Backtrack (remove queen)
    
    return False  # No valid position found

def print_board(board):
    for row in board:
        print(" ".join(["Q" if x == 1 else "." for x in row]))

def n_queens(n):
    board = [[0 for _ in range(n)] for _ in range(n)]
    
    if solve_n_queens(board, 0, n):
        print_board(board)
    else:
        print("No solution exists.")

# Example usage:
n_queens(4)  # Solve 4-Queens problem

Output:

Q . . .
. . Q .
. Q . .
. . . Q

Explanation:

The is_safe function checks if a queen can be safely placed at a given position. solve_n_queens uses backtracking to find a solution for placing all queens on the board. The n_queens function initializes the board and prints the solution or indicates no solution exists.

2. Sudoku Solver

The Sudoku problem requires you to fill a 9x9 grid with numbers from 1 to 9 so that every row, column, and 3x3 subgrid contains each number exactly once.

Code snippet:

def is_safe(board, row, col, num):
    # Check the row
    for x in range(9):
        if board[row][x] == num:
            return False
    
    # Check the column
    for x in range(9):
        if board[x][col] == num:
            return False
    
    # Check the 3x3 subgrid
    start_row, start_col = 3 * (row // 3), 3 * (col // 3)
    for i in range(3):
        for j in range(3):
            if board[i + start_row][j + start_col] == num:
                return False
    
    return True

def solve_sudoku(board):
    empty = find_empty_location(board)
    if not empty:
        return True  # Solution found
    
    row, col = empty
    
    for num in range(1, 10):
        if is_safe(board, row, col, num):
            board[row][col] = num
            
            if solve_sudoku(board):
                return True
            
            board[row][col] = 0  # Backtrack
    
    return False  # Trigger backtracking if no valid number

def find_empty_location(board):
    for i in range(9):
        for j in range(9):
            if board[i][j] == 0:
                return i, j
    return None  # No empty location found

def print_board(board):
    for row in board:
        print(" ".join(str(num) if num != 0 else '.' for num in row))

# Example usage with a partially filled Sudoku board
sudoku_board = [
    [5, 3, 0, 0, 7, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9]
]

if solve_sudoku(sudoku_board):
    print_board(sudoku_board)
else:
    print("No solution exists.")

Output:
5 3 4 6 7 8 9 1 2
6 7 2 1 9 5 3 4 8
1 9 8 3 4 2 5 6 7
8 5 9 7 6 1 4 2 3
4 2 6 8 5 3 7 9 1
7 1 3 9 2 4 8 5 6
9 6 1 5 3 7 2 8 4
2 8 7 4 1 9 6 3 5
3 4 5 2 8 6 1 7 9

Explanation:

The is_safe function checks whether a number can be placed in a given cell on the Sudoku board. The solve_sudokufunction uses backtracking to try filling the board with valid numbers. If an empty location is found, the function attempts to fill it, and if successful, the board is solved. If no solution exists, it triggers backtracking.

3. Subset Sum Problem

The subset sum problem involves finding a subset of numbers from a given set that adds up to a specific target sum. For instance, from an array of [10,5,6,9,3,4], you have to achieve the target digit 9. A subset [4,5] gives you the target digit.

Code snippet:

def subset_sum(nums, target, current=[], index=0):
    # If the current sum equals target, print the current subset
    if sum(current) == target:
        print(current)
        return True
    
    # If the current sum exceeds the target, return False
    if sum(current) > target:
        return False
    
    for i in range(index, len(nums)):
        current.append(nums[i])  # Include the number in the subset
        
        if subset_sum(nums, target, current, i + 1):  # Recur with the next number
            return True
        
        current.pop()  # Backtrack, exclude the number
    
    return False  # No valid subset found

# Example usage:
nums = [3, 34, 4, 12, 5, 2]
target = 9
if not subset_sum(nums, target):
    print("No subset found")

Output:

[4, 5]

Explanation:

The subset_sum function finds a subset of numbers from a list that sum up to a target. It recursively explores possible subsets, and if the sum matches the target, it prints the subset. If no valid subset is found, it returns "No subset found." The function uses backtracking to explore all possible subsets efficiently.

Next, check out the essential libraries used for algorithm development.

Also read: Python for Data Science Cheat Sheet: Pandas, NumPy, Matplotlib & Key Functions

What Are the Key Libraries for Algorithm Development in Python?

Python offers several libraries that simplify the implementation of algorithms, making it easier to work with complex data structures, algorithms, and optimization techniques.

Here are the essential Python libraries that can help in algorithm development.

1. NumPy

NumPy provides high-performance arrays and matrices, making it suitable for algorithms related to mathematical computations, optimization, and data analysis. For instance, NumPy is useful for algorithms like Gaussian elimination.

Code snippet:

import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
result = np.dot(a, b)  # Dot product
print(result)

Output:

32

Explanation:

The code calculates the dot product of two arrays a and b. The dot product is the sum of the element-wise multiplications of corresponding elements. For arrays [1, 2, 3] and [4, 5, 6], the result is (1×4)+(2×5)+(3×6)=32(1×4)+(2×5)+(3×6)=32. Thus, the output is 32.

2. SciPy

SciPy is suitable for scientific and technical computing. It is used for optimization, signal processing, graph theory, and solving differential equations. For example, SciPy is suitable for graph algorithms.

Code snippet:

from scipy.optimize import minimize

def func(x):
    return x**2 + 5

result = minimize(func, 0)
print(result.x)  # Minimum value of the function

Output:

[0.]

Explanation:

The function f(x) = x^2 + 5 has its minimum at x = 0, where the value of the function is 5. The minimize function finds that the minimum occurs at x = 0 and returns this value. Thus, the output is 0.0, indicating the point at which the function reaches its minimum.

3. NetworkX

NetworkX is suitable for complex graphs and networks. It has easy-to-use tools for graph creation, manipulation, and various graph algorithms. NetworkX is perfect for graph algorithms like DFS, BFS, and Dijkstra.

Code snippet:

import networkx as nx
G = nx.Graph()
G.add_edges_from([(1, 2), (2, 3), (3, 4)])
path = nx.shortest_path(G, source=1, target=4)
print(path)  # Output: [1, 2, 3, 4]

Output:

[1, 2, 3, 4]

Explanation:

The nx.shortest_path function calculates the shortest path from node 1 to node 4 by exploring the graph's edges. The output [1, 2, 3, 4] represents the sequence of nodes that forms the shortest path, indicating that the path goes from node 1 to node 2, then node 3, and finally reaches node 4.

4. SymPy

SymPy can simplify algebraic expressions, solve equations, perform symbolic differentiation and integration, and more. It can also implement algorithms in symbolic form.

Code snippet:

from sympy import symbols, Eq, solve

x = symbols('x')
equation = Eq(x**2 - 5, 0)
solution = solve(equation)
print(solution)  # Solve x^2 = 5

Output:

[-sqrt(5), sqrt(5)]

Explanation:

The solve function solves the equation x^2 = 5 and returns two solutions: -sqrt(5) and sqrt(5). These are the two values of x that satisfy the equation, where sqrt(5) represents the square root of 5.

5. Pandas

Pandas is a powerful library for data manipulation and analysis. It is suitable for handling algorithms for data processing, machine learning, or optimization. For instance, the Pandas library can pre-process data before applying machine learning.

Code snippet:

import pandas as pd

data = {'A': [1, 2, 3], 'B': [4, 5, 6]}
df = pd.DataFrame(data)
print(df.mean())  # Compute the mean of each column

Output:

A    2.0

B    5.0

dtype: float64

Explanation:

The mean() function calculates the average of each column in the DataFrame. For column 'A', the mean is (1+2+3)/3 = 2.0, and for column 'B', the mean is (4+5+6)/3 = 5.0. These are the computed means for each respective column. 

Take your career to new heights with India’s first 1-Year Master’s in Artificial Intelligence and Data Science from Jindal Global University, ranked India’s top private university. Gain practical experience with over 15 industry tools, work on real-world projects, and earn a prestigious Microsoft Certification.

Also read: 25+ Python GUI Projects to Up Your Programming Skills

Now that you have gained valuable insights into different algorithms, let’s examine how Python can be suitable for algorithm development and implementation.

Is Python Suitable for Algorithm Development and Implementation?

Python’s simple syntax, extensive standard library, and rich ecosystem provide a wide range of tools for data structures & algorithms and numerical computations. However, Python's interpreted nature can lead to slower execution speeds compared to compiled languages like C++ or Java.

Here’s a brief idea about the strengths and weaknesses of Python for algorithm development.

Python’s Strengths for Algorithm Development

Python’s strengths in algorithm development can be broken down into the following points.

  • Simple syntax and readability

Python's syntax is straightforward, intuitive, and closely resembles English, which makes it easy to learn and write code. 

  • Large library support

Python offers a massive collection of standard libraries like Pandas and NumPy for different tasks, such as data manipulation, machine learning, and web development. 

  • Strong community support

Python has one of the largest and most active communities of developers, contributors, and learners. Examples include Python.org and Stack Overflow.

  • Cross-platform compatibility

Python is platform-independent. The same Python code can run without modification on different operating systems (Windows, macOS, Linux).

  • Built-in garbage collection

Python’s automatic memory management and garbage collection free up memory space when objects are no longer needed, reducing the risk of memory leaks. 

  • Rich ecosystem for AI and ML

Python is most appropriate for new technologies like Artificial Intelligence (AI) and Machine Learning (ML) due to powerful libraries like TensorFlow, Keras, PyTorch, Scikit-learn, and OpenCV.

  • Readable debugging tools

Python’s built-in debugging tools, such as pdb and the Python Debugger, make it easy to troubleshoot and fix issues in algorithm implementation.

  • Dynamic typing

Python’s variables need not be declared with a specific data type, which allows for more flexibility and concise code. 

Python’s Limitations for Algorithm Development

Here are some limitations of using Python for algorithm development.

  • Slower execution speed

Python is an interpreted language, which means it executes slower than compiled languages like C. For example, sorting large datasets can be slower in Python. 

  • Higher memory usage

Python’s dynamic data types, high-level abstractions, and garbage collection can increase memory usage. For example, handling large graphs is less memory-efficient in Python compared to C++.

  • Limited for low-level programming

Python is unsuitable for low-level system programming tasks, such as writing operating systems or real-time embedded systems. For example, developing a real-time control system for hardware. 

  • Global Interpreter Lock (GIL)

The Global Interpreter Lock (GIL) mechanism ensures that only one thread executes Python bytecode at a time, limiting the parallel execution of multi-threaded Python programs. 

  • Not ideal for real-time systems

Python's slower execution time makes it unsuitable for real-time systems. For example, Python-based controlling machinery in an industrial setting is not feasible.

  • Dependency on external libraries

Python relies on external libraries, such as NumPy or SciPy, to achieve performance. This raises issues like version compatibility. 

  • Lack of native optimization

Python’s absence of built-in low-level optimizations can limit the performance of certain algorithms. For example, Python cannot handle algorithms for ray tracing in graphics programming.

Also Read: Top 10 Reasons Why Python is Popular With Developers 2025

Now, let's see how upGrad can help you in your journey for learning data structures & algorithms in Python.

Learn Data Structures & Algorithms in Python with upGrad

Learning data structures & algorithms (DSA) in Python is crucial for improving problem-solving skills and writing efficient code. In 2025, Python continues to be a top choice for DSA due to its simplicity and versatility. Understanding key structures like arrays, trees, and graphs, along with algorithms like sorting and searching, will set you apart in tech.   

To pursue a career in Python programming, you need to acquire specialized skills. upGrad's free Python courses equip you with industry-specific skills, preparing you for a successful career in Python programming.

Here are some popular courses offered by upGrad in Python programming.

Do you need help deciding which course to take to become a Python programmer? 

Connect with upGrad Counselling for personalized guidance. You can also visit one of our offline centres to choose the best learning path tailored to your career goals.

Unlock the power of data with our popular Data Science courses, designed to make you proficient in analytics, machine learning, and big data!

Elevate your career by learning essential Data Science skills such as statistical modeling, big data processing, predictive analytics, and SQL!

Stay informed and inspired with our popular Data Science articles, offering expert insights, trends, and practical tips for aspiring data professionals!

References:
https://en.wikipedia.org/wiki/Powersort

Frequently Asked Question (FAQs)

1. How do Python’s lists and arrays differ in terms of performance and memory consumption when handling large datasets?

2. What is the difference in time complexity when using dictionaries versus lists for membership tests in Python?

3. What are the trade-offs of using Python's built-in deque versus a list for queue implementations?

4. How does the garbage collection mechanism in Python affect the performance of dynamically allocated data structures & algorithms like linked lists?

5. In graph algorithms, why is Python’s adjacency list representation preferred over an adjacency matrix in terms of both time and space complexity?

6. How do you manage and optimize recursion in Python when working with data structures & algorithms like trees or graphs to avoid performance bottlenecks?

7. How does the Python GIL (Global Interpreter Lock) affect multithreading and the performance of data structures & algorithms like heaps in parallel computations?

8. What are the underlying complexities of implementing a balanced binary search tree (BST) in Python and how does it compare to AVL or Red-Black trees?

9. How does Python handle dynamic memory allocation for data structures & algorithms like arrays, lists, and dictionaries, and what are the implications for real-time applications?

10. How do Python’s built-in sorting algorithms like Timsort perform on different data structures & algorithms such as lists, sets, and dictionaries?

11. How do Python’s data structures & algorithms like lists and dictionaries compare with Java’s counterparts in terms of performance and memory efficiency?

Rohit Sharma

763 articles published

Rohit Sharma shares insights, skill building advice, and practical tips tailored for professionals aiming to achieve their career goals.

Get Free Consultation

+91

By submitting, I accept the T&C and
Privacy Policy

Start Your Career in Data Science Today

Top Resources

Recommended Programs

IIIT Bangalore logo
bestseller

The International Institute of Information Technology, Bangalore

Executive Diploma in Data Science & AI

Placement Assistance

Executive PG Program

12 Months

Liverpool John Moores University Logo
bestseller

Liverpool John Moores University

MS in Data Science

Dual Credentials

Master's Degree

17 Months

upGrad Logo

Certification

3 Months