Data Structures Explained — Arrays, Linked Lists, Stacks & Queues
Data structures are ways of organizing and storing data so it can be used efficiently — the foundation of every software application you use.
What You’ll Learn
In this tutorial, you’ll learn about arrays, linked lists, stacks, and queues — how they work, when to use each, and how to implement them in Python.
Why It Matters
Every program uses data structures. The “Undo” feature in your text editor uses a stack. Print jobs are managed with a queue. Your music playlist is a linked list. Choosing the right data structure can make or break your application’s performance.
Real-World Use
When you press Ctrl+Z in a text editor, the application pops the last action from a stack. When you send a document to the printer, it waits in a queue. Your phone’s contact list is an array. Understanding these structures helps you write better software.
flowchart TD
A[Data Structures] --> B[Linear]
A --> C[Non-Linear]
B --> D[Arrays]
B --> E[Linked Lists]
B --> F[Stacks]
B --> G[Queues]
C --> H[Trees]
C --> I[Graphs]
Arrays
An array is the simplest data structure — a contiguous block of memory where each element is stored next to its neighbor.
Think of an array like a row of lockers at a gym. Each locker has a number (index), and you can open any locker directly if you know its number. You don’t need to search through all the lockers — just go to number 7.
# Arrays in Python (using lists)
fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
# Access by index - O(1) - instant!
print(f"Third fruit: {fruits[2]}") # Index 2 = Cherry
# Append to end - O(1) amortized
fruits.append("Fig")
print(f"After append: {fruits}")
# Insert at beginning - O(n) - requires shifting elements
fruits.insert(0, "Apricot")
print(f"After insert at 0: {fruits}")
# Remove from middle - O(n) - requires shifting elements
fruits.remove("Date")
print(f"After removal: {fruits}")Expected output:
Third fruit: Cherry
After append: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig']
After insert at 0: ['Apricot', 'Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig']
After removal: ['Apricot', 'Apple', 'Banana', 'Cherry', 'Elderberry', 'Fig']Array operations and their Big O:
| Operation | Time | Why |
|---|---|---|
| Access by index | O(1) | Direct memory address calculation |
| Append to end | O(1) | Add at the end (amortized) |
| Insert at beginning | O(n) | Every element must shift right |
| Remove from middle | O(n) | Every element must shift left |
| Search | O(n) | Must check each element until found |
When to use arrays: When you need fast access by index and mostly add/remove at the end. Good for contact lists, high scores, and any fixed-size collection.
Linked Lists
A linked list stores elements as nodes, where each node contains data and a pointer to the next node. Unlike arrays, elements aren’t stored in contiguous memory.
Think of a linked list like a treasure hunt. Each clue tells you where to find the next clue. To find the last clue, you must follow the chain from the beginning.
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
"""Add to the end - O(n)"""
new_node = Node(data)
if not self.head:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
def prepend(self, data):
"""Add to the beginning - O(1)"""
new_node = Node(data)
new_node.next = self.head
self.head = new_node
def display(self):
"""Print all elements"""
current = self.head
elements = []
while current:
elements.append(str(current.data))
current = current.next
print(" -> ".join(elements))
# Usage
playlist = LinkedList()
playlist.append("Song A")
playlist.append("Song B")
playlist.prepend("Favorite Song")
playlist.append("Song C")
print("Music Playlist:")
playlist.display()Expected output:
Music Playlist:
Favorite Song -> Song A -> Song B -> Song CLinked list operations:
| Operation | Time | Why |
|---|---|---|
| Insert at beginning | O(1) | Just update head pointer |
| Insert at end | O(n) | Must traverse to last node |
| Access by index | O(n) | Must traverse from head |
| Search | O(n) | Must traverse the list |
When to use linked lists: When you need fast insertions/deletions at the beginning or middle. Music playlists, image viewers (prev/next), and blockchain blocks use linked lists.
Array vs Linked List
| Aspect | Array | Linked List |
|---|---|---|
| Memory | Contiguous (one block) | Scattered (nodes linked by pointers) |
| Access | O(1) by index | O(n) traverse |
| Insert start | O(n) shift | O(1) update head |
| Insert end | O(1) amortized | O(n) traverse |
| Memory overhead | Low | Higher (stores pointers) |
Stacks
A stack follows Last In, First Out (LIFO) — like a stack of plates. You add plates to the top and take plates from the top. The last plate you put on is the first one you take off.
class Stack:
def __init__(self):
self.items = []
def push(self, item):
"""Add to top - O(1)"""
self.items.append(item)
def pop(self):
"""Remove from top - O(1)"""
if self.is_empty():
return None
return self.items.pop()
def peek(self):
"""View top without removing - O(1)"""
if self.is_empty():
return None
return self.items[-1]
def is_empty(self):
return len(self.items) == 0
# Simulate the Undo feature
editor = Stack()
print("Typing some text...")
editor.push("Hello")
editor.push("Hello World")
editor.push("Hello World!")
print(f"Current text: '{editor.peek()}'")
print("\nUndo last action...")
editor.pop()
print(f"After undo: '{editor.peek()}'")
print("Undo again...")
editor.pop()
print(f"After undo: '{editor.peek()}'")Expected output:
Typing some text...
Current text: 'Hello World!'
Undo last action...
After undo: 'Hello World'
Undo again...
After undo: 'Hello'Stack operations: All operations are O(1).
Real-world uses of stacks:
- Undo/Redo in text editors
- Browser history (back button)
- Function call stack — Python uses a stack to track function calls
- Expression evaluation — compilers use stacks to evaluate arithmetic
Queues
A queue follows First In, First Out (FIFO) — like a line at a coffee shop. The first person in line is the first person served.
from collections import deque
class Queue:
def __init__(self):
self.items = deque()
def enqueue(self, item):
"""Add to back - O(1)"""
self.items.append(item)
def dequeue(self):
"""Remove from front - O(1)"""
if self.is_empty():
return None
return self.items.popleft()
def peek(self):
"""View front without removing - O(1)"""
if self.is_empty():
return None
return self.items[0]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
# Simulate a print queue
printer = Queue()
print("Sending print jobs...")
printer.enqueue("Report.pdf")
printer.enqueue("Invoice.pdf")
printer.enqueue("Photo.jpg")
print(f"Queue size: {printer.size()}")
print("\nProcessing print jobs:")
while not printer.is_empty():
job = printer.dequeue()
print(f"Printing: {job}")Expected output:
Sending print jobs...
Queue size: 3
Processing print jobs:
Printing: Report.pdf
Printing: Invoice.pdf
Printing: Photo.jpgQueue operations: All operations are O(1) when using collections.deque.
Real-world uses of queues:
- Print queues — documents wait their turn
- Task scheduling — CPU schedules processes
- Message queues — RabbitMQ, Kafka
- Breadth-first search — graph traversal algorithm
Security Applications
Stack-based buffer overflow — One of the most famous security vulnerabilities. When a program writes more data to a stack buffer than it can hold, the overflow can overwrite the return address, allowing attackers to execute arbitrary code.
Queue-based rate limiting — APIs use queues to enforce rate limits. Each request enters a queue, and requests over the limit are dropped to prevent abuse.
Array bounds checking — Accessing array indices outside bounds (like arr[100] for a 10-element array) is a common vulnerability. Python catches this, but C and C++ don’t — leading to buffer overflow bugs.
Common Mistakes Beginners Make
1. Confusing stack and queue
Stack = LIFO (last in, first out). Queue = FIFO (first in, first out). Remember: stack of plates, queue of people.
2. Using an array when a linked list is better
If you need frequent insertions at the beginning, use a linked list. Arrays require O(n) shifting for this.
3. Forgetting that Python lists are dynamic
Python lists handle resizing automatically. You don’t need to specify size upfront like in C or Java.
4. Not using deque for queues
list.pop(0) is O(n). Use collections.deque for O(1) queue operations.
5. Infinite loops in linked lists
If a linked list has a cycle (a node pointing back to a previous node), traversal never ends. Always test with small examples.
Practice Questions
What’s the difference between an array and a linked list? Arrays store elements in contiguous memory, offering O(1) index access but O(n) insertions at the start. Linked lists store nodes with pointers, offering O(1) insertion at the start but O(n) index access.
What does LIFO stand for? Last In, First Out. The last element added to a stack is the first one removed.
Give three real-world uses of a queue. Print queues, CPU task scheduling, and message queue systems.
Why does inserting at the beginning of an array take O(n) time? All existing elements must shift one position to the right to make room.
What data structure would you use for a browser’s back button? A stack. Each visited page is pushed onto the stack. The back button pops the top page.
Challenge
Implement a deque (double-ended queue) that supports O(1) insertion and removal at both ends. Use it to check if a word is a palindrome (“racecar” reads the same forward and backward).
Real-World Task
Open your browser and visit 5 websites. Press the back button 3 times. Can you trace the stack operations? Which sites were pushed and popped?
FAQ
Try It Yourself
Mini Project: Task Scheduler
Build a simple task scheduler using a queue:
- Users can add tasks with priorities
- A worker processes tasks in FIFO order
- Track completed and pending tasks
Security angle: Message queues are used in security systems to process threat alerts. Each alert enters a queue and is analyzed by workers in order, ensuring no threat is missed.
What’s Next
Before moving on, you should understand:
- Array vs linked list trade-offs
- Stack (LIFO) vs Queue (FIFO) behavior
- Real-world applications of each data structure
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
What’s Next
Congratulations on completing this Data Structures tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro