LoginSign Up
Programming Tutorial

Understanding Data Structures in Python - Complete Guide

Mihir Joshi
January 15, 2025
18 min read

What You'll Learn

  • Core concepts of Stack, Queue, Linked List, Trees, and Graphs
  • Python implementations with working code examples
  • Real-world applications and use cases
  • Time and space complexity analysis
  • CBSE Class 12 exam preparation tips for data structures
  • Common interview questions and answers

Introduction to Data Structures

Data Structures are specialized formats for organizing, storing, and managing data efficiently. They form the foundation of computer science and enable efficient data manipulation for various operations like searching, sorting, inserting, and deleting.

Think of data structures as containers that hold data in specific ways to make certain operations faster or more efficient. Just like you organize your bookshelf differently than your wardrobe, different types of data require different organizational structures.

Types of Data Structures:

  • Linear Data Structures: Elements arranged sequentially (Arrays, Lists, Stacks, Queues, Linked Lists)
  • Non-Linear Data Structures: Elements arranged hierarchically or networked (Trees, Graphs)
  • Static Data Structures: Fixed size (Arrays)
  • Dynamic Data Structures: Size can change (Linked Lists, Stacks, Queues)

Why Data Structures Matter

Understanding data structures is crucial for several reasons:

  • Efficiency: Proper data structure choice can reduce time complexity from O(n²) to O(log n) or even O(1)
  • Problem Solving: Many real-world problems have optimal solutions using specific data structures
  • Interview Success: Tech interviews heavily focus on data structures and algorithms
  • Better Code: Writing organized, maintainable, and scalable code
  • System Design: Essential for designing databases, operating systems, compilers, and network protocols

Stack - LIFO (Last In First Out) Principle

A Stack is a linear data structure where elements are added and removed from the same end, called the "top". It follows the LIFO principle - the last element inserted is the first one to be removed.

Real-World Examples:

  • Browser History: Back button uses stack - most recent page visited is shown first
  • Undo Functionality: Text editors use stack to track changes
  • Function Calls: Program execution uses call stack to track function calls
  • Expression Evaluation: Converting and evaluating mathematical expressions

Stack Operations:

  • Push: Add element to top of stack
  • Pop: Remove element from top of stack
  • Peek/Top: View top element without removing
  • isEmpty: Check if stack is empty
  • Size: Get number of elements in stack

Stack Implementation in Python:

# Stack implementation using Python list class Stack: def __init__(self): self.items = [] def push(self, item): """Add item to top of stack""" self.items.append(item) print(f"Pushed: {item}") def pop(self): """Remove and return top item""" if self.is_empty(): return "Stack is empty" return self.items.pop() def peek(self): """View top item without removing""" if self.is_empty(): return "Stack is empty" return self.items[-1] def is_empty(self): """Check if stack is empty""" return len(self.items) == 0 def size(self): """Return number of items""" return len(self.items) def display(self): """Display all items""" print("Stack (top to bottom):", self.items[::-1]) # Example usage stack = Stack() stack.push(10) stack.push(20) stack.push(30) stack.display() # Output: [30, 20, 10] print("Popped:", stack.pop()) # Output: 30 print("Top element:", stack.peek()) # Output: 20

Practical Application - Balanced Parentheses:

def is_balanced(expression): """Check if parentheses are balanced using stack""" stack = [] opening = "({[" closing = "})]" pairs = {')': '(', '}': '{', ']': '['} for char in expression: if char in opening: stack.append(char) elif char in closing: if not stack or stack.pop() != pairs[char]: return False return len(stack) == 0 # Test cases print(is_balanced("({[]})")) # True print(is_balanced("({[})")) # False print(is_balanced("((()))")) # True

Queue - FIFO (First In First Out) Principle

A Queue is a linear data structure where elements are added at one end (rear) and removed from the other end (front). It follows the FIFO principle - the first element inserted is the first one to be removed.

Real-World Examples:

  • Printer Queue: Print jobs are processed in order they were received
  • Customer Service: Customers served in order of arrival
  • CPU Scheduling: Processes waiting for CPU time
  • BFS Algorithm: Breadth-First Search uses queue for traversal

Queue Operations:

  • Enqueue: Add element to rear of queue
  • Dequeue: Remove element from front of queue
  • Front: View front element without removing
  • Rear: View last element
  • isEmpty: Check if queue is empty

Queue Implementation in Python:

# Queue implementation using Python list class Queue: def __init__(self): self.items = [] def enqueue(self, item): """Add item to rear of queue""" self.items.append(item) print(f"Enqueued: {item}") def dequeue(self): """Remove and return front item""" if self.is_empty(): return "Queue is empty" return self.items.pop(0) def front(self): """View front item without removing""" if self.is_empty(): return "Queue is empty" return self.items[0] def rear(self): """View rear item""" if self.is_empty(): return "Queue is empty" return self.items[-1] def is_empty(self): """Check if queue is empty""" return len(self.items) == 0 def size(self): """Return number of items""" return len(self.items) def display(self): """Display all items""" print("Queue (front to rear):", self.items) # Example usage queue = Queue() queue.enqueue(10) queue.enqueue(20) queue.enqueue(30) queue.display() # Output: [10, 20, 30] print("Dequeued:", queue.dequeue()) # Output: 10 print("Front element:", queue.front()) # Output: 20
Performance Note: Using pop(0) for dequeue is O(n) because all elements need to shift. For better performance, use collections.deque which provides O(1) operations for both ends.

Efficient Queue Using collections.deque:

from collections import deque class EfficientQueue: def __init__(self): self.items = deque() def enqueue(self, item): self.items.append(item) # O(1) def dequeue(self): if self.is_empty(): return "Queue is empty" return self.items.popleft() # O(1) def is_empty(self): return len(self.items) == 0

Linked Lists

A Linked List is a linear data structure where elements (nodes) are stored in sequence, but not in contiguous memory locations. Each node contains data and a reference (link) to the next node.

Advantages over Arrays:

  • Dynamic Size: Can easily grow or shrink
  • Efficient Insertion/Deletion: O(1) if position is known (no shifting needed)
  • Memory Efficient: Uses only required memory

Disadvantages:

  • Random Access: O(n) - must traverse from beginning
  • Extra Memory: Stores reference to next node
  • Cache Inefficient: Non-contiguous memory

Singly Linked List Implementation:

# Node class class Node: def __init__(self, data): self.data = data self.next = None # Linked List class class LinkedList: def __init__(self): self.head = None def insert_at_beginning(self, data): """Insert node at beginning - O(1)""" new_node = Node(data) new_node.next = self.head self.head = new_node def insert_at_end(self, data): """Insert node at end - O(n)""" new_node = Node(data) if self.head is None: self.head = new_node return current = self.head while current.next: current = current.next current.next = new_node def delete_node(self, key): """Delete node with given data - O(n)""" current = self.head # If head needs to be deleted if current and current.data == key: self.head = current.next current = None return # Search for the node prev = None while current and current.data != key: prev = current current = current.next # If key not found if current is None: return # Unlink the node prev.next = current.next current = None def search(self, key): """Search for a node - O(n)""" current = self.head while current: if current.data == key: return True current = current.next return False def display(self): """Display all nodes""" current = self.head elements = [] while current: elements.append(current.data) current = current.next print(" -> ".join(map(str, elements))) # Example usage ll = LinkedList() ll.insert_at_end(10) ll.insert_at_end(20) ll.insert_at_beginning(5) ll.display() # Output: 5 -> 10 -> 20 ll.delete_node(10) ll.display() # Output: 5 -> 20

Binary Trees

A Binary Tree is a hierarchical data structure where each node has at most two children, referred to as left child and right child. The topmost node is called the root.

Types of Binary Trees:

  • Full Binary Tree: Every node has 0 or 2 children
  • Complete Binary Tree: All levels filled except possibly the last, which fills from left
  • Perfect Binary Tree: All internal nodes have 2 children, all leaves at same level
  • Binary Search Tree (BST): Left child < parent < right child

Binary Tree Traversals:

  • Inorder (Left-Root-Right): Gives sorted order in BST
  • Preorder (Root-Left-Right): Useful for copying tree
  • Postorder (Left-Right-Root): Useful for deleting tree
  • Level Order: Level by level traversal (uses queue)

Binary Search Tree Implementation:

# Node class for BST class TreeNode: def __init__(self, data): self.data = data self.left = None self.right = None # Binary Search Tree class class BinarySearchTree: def __init__(self): self.root = None def insert(self, data): """Insert data into BST""" if self.root is None: self.root = TreeNode(data) else: self._insert_recursive(self.root, data) def _insert_recursive(self, node, data): if data < node.data: if node.left is None: node.left = TreeNode(data) else: self._insert_recursive(node.left, data) else: if node.right is None: node.right = TreeNode(data) else: self._insert_recursive(node.right, data) def inorder_traversal(self, node): """Inorder: Left-Root-Right""" if node: self.inorder_traversal(node.left) print(node.data, end=" ") self.inorder_traversal(node.right) def preorder_traversal(self, node): """Preorder: Root-Left-Right""" if node: print(node.data, end=" ") self.preorder_traversal(node.left) self.preorder_traversal(node.right) def search(self, data): """Search for data in BST - O(log n) average""" return self._search_recursive(self.root, data) def _search_recursive(self, node, data): if node is None or node.data == data: return node is not None if data < node.data: return self._search_recursive(node.left, data) return self._search_recursive(node.right, data) # Example usage bst = BinarySearchTree() values = [50, 30, 70, 20, 40, 60, 80] for val in values: bst.insert(val) print("Inorder traversal:") bst.inorder_traversal(bst.root) # Output: 20 30 40 50 60 70 80 print("\nSearch 40:", bst.search(40)) # True print("Search 100:", bst.search(100)) # False

Graphs Basics

A Graph is a non-linear data structure consisting of vertices (nodes) and edges connecting them. Graphs are used to represent networks, relationships, and connections.

Types of Graphs:

  • Directed vs Undirected: Edges have direction or not
  • Weighted vs Unweighted: Edges have weights/costs or not
  • Cyclic vs Acyclic: Contains cycles or not
  • Connected vs Disconnected: All vertices reachable or not

Graph Representations:

  • Adjacency Matrix: 2D array, space O(V²)
  • Adjacency List: Dictionary of lists, space O(V+E)

Simple Graph Implementation:

# Graph using Adjacency List class Graph: def __init__(self): self.graph = {} def add_vertex(self, vertex): """Add a vertex to graph""" if vertex not in self.graph: self.graph[vertex] = [] def add_edge(self, vertex1, vertex2): """Add an edge between two vertices (undirected)""" if vertex1 in self.graph and vertex2 in self.graph: self.graph[vertex1].append(vertex2) self.graph[vertex2].append(vertex1) def display(self): """Display graph""" for vertex in self.graph: print(f"{vertex} -> {', '.join(map(str, self.graph[vertex]))}") # Example usage g = Graph() vertices = ['A', 'B', 'C', 'D'] for v in vertices: g.add_vertex(v) g.add_edge('A', 'B') g.add_edge('A', 'C') g.add_edge('B', 'D') g.add_edge('C', 'D') g.display() # Output: # A -> B, C # B -> A, D # C -> A, D # D -> B, C

Time and Space Complexity

Understanding complexity helps choose the right data structure for your use case:

Data Structure Access Search Insertion Deletion Space
Array O(1) O(n) O(n) O(n) O(n)
Stack O(n) O(n) O(1) O(1) O(n)
Queue O(n) O(n) O(1) O(1) O(n)
Linked List O(n) O(n) O(1)* O(1)* O(n)
Binary Search Tree O(log n) O(log n) O(log n) O(log n) O(n)
Hash Table O(1) O(1) O(1) O(1) O(n)

* For linked lists, insertion/deletion is O(1) if position is known, otherwise O(n) to find position

Choosing the Right Data Structure

Select data structures based on your requirements:

Use Stack When:

  • You need LIFO behavior (undo/redo, browser history)
  • Implementing recursion or DFS algorithm
  • Expression evaluation or syntax parsing
  • Backtracking problems (maze solving, N-queens)

Use Queue When:

  • You need FIFO behavior (task scheduling, printer queue)
  • BFS algorithm implementation
  • Handling asynchronous requests
  • Cache implementation (LRU cache)

Use Linked List When:

  • Frequent insertions/deletions in middle
  • Size changes frequently and unpredictably
  • Implementing other data structures (stack, queue)
  • Memory fragmentation is an issue

Use Trees When:

  • Hierarchical data (file systems, organization charts)
  • Need fast search, insertion, deletion (BST)
  • Implementing dictionaries or databases
  • Priority queues (heap trees)

Use Graphs When:

  • Representing networks (social, computer, road)
  • Finding shortest paths (GPS navigation)
  • Web crawling and link analysis
  • Recommendation systems

Data Structures in CBSE Class 12 Syllabus

For CBSE Class 12 Computer Science, focus on these topics:

Covered in Syllabus:

  • Stack: Implementation using list, push, pop, peek operations
  • Queue: Implementation using list, enqueue, dequeue operations
  • Understanding: LIFO vs FIFO principles, applications

Exam Preparation Tips:

  1. Write Complete Code: Practice writing full stack/queue implementation from scratch
  2. Understand Operations: Know what each operation does and its complexity
  3. Real-World Examples: Memorize 2-3 applications for each data structure
  4. Trace Code: Practice predicting output of given stack/queue programs
  5. Error Finding: Be ready to identify errors in given code
  6. Diagram Drawing: Draw stack/queue states after each operation

Common Exam Questions:

  • Write a function to push/pop elements in stack
  • Write a function to insert/delete elements in queue
  • Predict output of given stack/queue program
  • Find errors in given stack/queue implementation
  • Explain LIFO/FIFO with examples
  • Give 3 real-world applications of stack/queue

Study Strategy for Exams

  • Practice writing stack/queue code at least 10 times without looking
  • Create a small notebook with all operation codes for quick revision
  • Solve all NCERT and sample paper questions on data structures
  • Watch your indentation - Python is strict about it!
  • Remember edge cases: empty stack/queue, single element scenarios
  • Use meaningful variable names in exams for better readability

Frequently Asked Questions

What is the difference between Stack and Queue?

Stack follows LIFO (Last In First Out) principle - the last element added is the first to be removed, like a stack of plates. Queue follows FIFO (First In First Out) principle - the first element added is the first to be removed, like people standing in a line. Stack has one end for operations (top), while Queue has two ends (front for removal, rear for addition). Real-world examples: Stack - browser back button, undo feature; Queue - printer jobs, customer service line.

When should I use a Linked List instead of an Array?

Use Linked List when: (1) You need frequent insertions/deletions in the middle - Arrays require shifting elements making it O(n), while Linked Lists can do it in O(1) if position is known, (2) Size changes unpredictably - Arrays have fixed size or expensive resizing, Linked Lists grow dynamically, (3) You don't need random access - If you rarely access elements by index, the O(n) access time of Linked Lists won't hurt. Use Arrays when you need fast random access O(1), better cache performance, or when size is relatively stable.

What is time complexity and why does it matter?

Time complexity describes how the runtime of an algorithm grows as input size increases. It's expressed using Big O notation: O(1) constant time, O(log n) logarithmic, O(n) linear, O(n²) quadratic. It matters because the difference is dramatic at scale - for 1 million elements, O(1) takes 1 operation, O(n) takes 1 million, O(n²) takes 1 trillion! Choosing the right data structure can reduce time from hours to milliseconds. For example, searching in a sorted array with binary search is O(log n) instead of O(n) with linear search - for 1 million items, that's 20 operations vs 1 million.

How is Binary Search Tree different from a regular Binary Tree?

A Binary Tree is any tree where each node has at most 2 children, with no ordering requirement. A Binary Search Tree (BST) is a special Binary Tree with a strict ordering property: for every node, all values in the left subtree are smaller, and all values in the right subtree are larger. This ordering enables efficient operations: search, insert, and delete are O(log n) in balanced BSTs vs O(n) in regular Binary Trees. BSTs are used for implementing dictionaries, sets, and databases. Example: in BST with root 50, left child might be 30, right child 70 - this ordering must maintain throughout the tree.

Do I need to learn all data structures for CBSE Class 12 exam?

No! CBSE Class 12 Computer Science syllabus specifically covers only Stack and Queue. You need to know: (1) Implementation of Stack using Python list, (2) Implementation of Queue using Python list, (3) Operations: push/pop for stack, enqueue/dequeue for queue, (4) LIFO and FIFO principles, (5) Real-world applications, and (6) Writing code to solve problems using these structures. Linked Lists, Trees, and Graphs are NOT in CBSE syllabus, though they're helpful for competitive programming and college. Focus your exam preparation on mastering Stack and Queue thoroughly - write code 10+ times, understand each operation, and practice NCERT questions.