4.6 Data Structures
Table of Contents
Why Data Structures Matter
In programming, a data structure is a specific way to organize and store data in memory so that you can use it efficiently.
You have already seen basic types like integers, strings, and booleans. Data structures let you:
- Group values together
- Access specific items quickly
- Insert and remove items efficiently
- Represent real world things, like a list of users or a mapping from usernames to emails
For backend development, choosing the right data structure can be the difference between a fast, scalable API and a slow, fragile one.
Key idea: A data structure is not just what you store, but how you store it and how you work with it (add, remove, search, update).
In this chapter we will focus on concepts, with simple examples in a generic, Python-like pseudocode, but not tied to any specific language details. The Python chapter will show you concrete syntax.
Collections vs Single Values
A single value:
age = 25
name = "Alice"A collection holds multiple values:
ages = [25, 32, 40]
names = ["Alice", "Bob", "Charlie"]Most common collection data structures:
| Structure | Also called | Typical usage |
|---|---|---|
| Array / List | Dynamic array, list | Ordered collection, index-based access |
| Dictionary | Map, hash map, object | Key/value lookups, configs, user records |
| Set | Hash set | Unique items, fast membership checks |
| Stack | LIFO | "Last added, first removed" |
| Queue | FIFO | "First added, first removed" |
| Linked list | Chain of nodes | Sequential access, cheap insert/remove |
You will meet these structures in almost every backend project, directly or indirectly.
Arrays / Lists
Concept
An array or list is an ordered collection of items. Each item has a numeric position called an index.
Example:
numbers = [10, 20, 30, 40]
# indexes: 0 1 2 3Access by index:
first = numbers[0] # 10
third = numbers[2] # 30When to use a list
- You care about the order of items.
- You want fast access by position.
- You often add items at the end.
Typical backend examples:
- List of user IDs in a search result.
- List of log entries in memory before writing to a file.
- Ordered list of steps in a workflow.
Basic operations on lists
Below is generic pseudocode that looks similar to Python.
Creating a list
empty = []
numbers = [1, 2, 3]
users = ["alice", "bob", "charlie"]Reading items
user = users[1] # "bob"Changing items
users[1] = "bobby"
# users is now ["alice", "bobby", "charlie"]Adding items
users.append("david") # add to the end
# ["alice", "bobby", "charlie", "david"]Removing items
last = users.pop() # removes and returns last item "david"
second = users.pop(1) # removes and returns item at index 1 "bobby"Looping over a list
for user in users:
print(user)or with indexes:
for i in range(len(users)):
print(i, users[i])Lists and order
Lists preserve order:
request_log = []
request_log.append("GET /users")
request_log.append("POST /login")
request_log.append("GET /me")
# processing in order:
for entry in request_log:
process(entry)Dictionaries / Maps
Concept
A dictionary (often called a map or hash map) stores data as key/value pairs.
Example:
user = {
"id": 123,
"username": "alice",
"email": "alice@example.com"
}You look up values by key, not by position.
When to use a dictionary
- You want to look up data by some identifier like username, email, ID.
- You do not care much about order.
- You want very fast "find by key" operations.
Typical backend examples:
- Map from user ID to user object in a cache.
- Configuration:
{"host": "localhost", "port": 5432}. - Mapping HTTP header names to values.
Basic operations on dictionaries
Creating a dictionary
user = {
"id": 1,
"username": "alice"
}Accessing values
username = user["username"] # "alice"Adding or changing values
user["email"] = "alice@example.com" # new key
user["username"] = "alice123" # update existing keySafe access
Often, you want to avoid errors when a key is missing.
email = user.get("email", None) # returns None if "email" not presentRemoving keys
removed = user.pop("email") # remove key and get its valueLooping over a dictionary
for key in user:
print(key, user[key])Or explicitly:
for key, value in user.items():
print(key, value)Example: Caching user sessions
Imagine a simple in-memory session store:
sessions = {}
def create_session(session_id, user_id):
sessions[session_id] = {"user_id": user_id}
def get_session(session_id):
return sessions.get(session_id)
def delete_session(session_id):
sessions.pop(session_id, None)
Here, sessions is a dictionary that maps a session_id to its session data. This pattern appears all the time in backend systems.
Rule of thumb: Use a dictionary when you need to quickly find a value by a meaningful key, like "username" or "order_id". Use a list when the numeric position of an item is enough.
Sets
Concept
A set is a collection of unique, unordered items.
Example:
user_ids = {1, 2, 3}Key properties:
- No duplicates.
- Order does not matter.
- Very fast checks like "is this item in the set?"
When to use a set
- You need to keep track of unique items.
- You often ask questions like "Is X present?".
- You do not care about order or duplicates.
Backend examples:
- Set of blocked user IDs.
- Set of permissions a user has.
- Set of IPs currently rate-limited.
Basic operations on sets
Creating a set
blocked_ips = {"10.0.0.1", "10.0.0.2"}
empty_set = set()Adding items
blocked_ips.add("10.0.0.3")If you add the same value again, nothing changes, because sets do not allow duplicates.
Checking membership
if "10.0.0.2" in blocked_ips:
deny_request()Removing items
blocked_ips.remove("10.0.0.1") # may error if not present
blocked_ips.discard("10.0.0.9") # no error if not presentSet operations
Sets support useful mathematical operations:
Let
admin_permissions = {"read_users", "delete_users", "ban_users"}
editor_permissions = {"read_users", "edit_posts"}- Union (everything any of them has):
all_permissions = admin_permissions.union(editor_permissions)
# {"read_users", "delete_users", "ban_users", "edit_posts"}- Intersection (what they both have):
shared = admin_permissions.intersection(editor_permissions)
# {"read_users"}- Difference (what admin has that editor does not):
only_admin = admin_permissions.difference(editor_permissions)
# {"delete_users", "ban_users"}These operations are very useful when implementing authorization logic.
Stacks
Concept
A stack is a collection where you can:
- Add an item on top (push).
- Remove the most recently added item (pop).
It follows the LIFO principle: Last In, First Out.
Think of a stack of plates. You put a plate on top and you also take the top plate first.
When to use a stack
- You need to reverse actions or keep track of history.
- You need nested processing like nested function calls or parsing.
Backend examples:
- Undo features (in an admin panel).
- Managing nested operations, like opening and closing database transactions.
- Simple parsing tasks, such as validating parentheses in user input.
Basic stack operations
Implementation using a list:
stack = []
def push(item):
stack.append(item)
def pop():
return stack.pop() # removes and returns last item
def peek():
if stack:
return stack[-1]
return NoneUsage:
push("first")
push("second")
top = pop() # "second"
top2 = pop() # "first"Example: Undo last admin action
A very simple idea:
actions_stack = []
def perform_action(action):
action.apply()
actions_stack.append(action)
def undo_last_action():
if actions_stack:
last = actions_stack.pop()
last.undo()Here the stack keeps track of actions in the order they were done. Undo always undoes the last action first.
Queues
Concept
A queue is a collection where you:
- Add items at the end.
- Remove items from the front.
It follows the FIFO principle: First In, First Out.
Think of people standing in line at a store. The first person in is the first person served.
When to use a queue
- You have tasks that should be processed in the order they arrive.
- You build background job systems.
- You handle incoming requests or messages.
Backend examples:
- Job queue for sending emails.
- Queue of messages from a message broker like RabbitMQ or Kafka.
- Queue of tasks for processing uploads.
Basic queue operations
Implementation with a list is possible, but in real code you will usually use a specialized queue structure to make operations efficient. Conceptually:
queue = []
def enqueue(item):
queue.append(item) # add to end
def dequeue():
if queue:
return queue.pop(0) # remove from front
return NoneUsage:
enqueue("job1")
enqueue("job2")
first = dequeue() # "job1"
second = dequeue() # "job2"Example: Simple email sending queue
email_queue = []
def queue_email(email):
email_queue.append(email)
def process_emails():
while email_queue:
email = email_queue.pop(0)
send_email(email)In real backend systems, you will use external queues and separate worker processes, but the basic idea is the same as this simple data structure.
Linked Lists
Concept
A linked list is a sequence of nodes. Each node stores:
- Some data.
- A reference to the next node.
Example:
[Data A] -> [Data B] -> [Data C] -> NoneIn many high-level languages you will not implement linked lists manually very often, because lists (dynamic arrays) are built in and very efficient. But it is useful to understand the idea.
When to use a linked list
- You need frequent insertions and deletions in the middle of the sequence.
- You do a lot of operations like "remove this node when you already have a reference to it".
Backend systems usually rely on existing libraries that may use linked lists internally.
Simple conceptual implementation
class Node:
def __init__(self, value):
self.value = value
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def add_front(self, value):
new_node = Node(value)
new_node.next = self.head
self.head = new_node
def iterate(self):
current = self.head
while current is not None:
print(current.value)
current = current.nextUsage:
ll = LinkedList()
ll.add_front("C")
ll.add_front("B")
ll.add_front("A")
# List is now A -> B -> C
ll.iterate() # prints A, then B, then CAgain, you usually will not code this from scratch in real backend projects, but understanding it helps when reading about algorithms and performance.
Choosing the Right Data Structure
Choosing the right structure often depends on what operations must be fast.
Some common tasks and recommended structures:
| Task / Question | Good data structure |
|---|---|
| "Keep items in a specific order" | List / array |
| "Get item number 5 quickly" | List / array |
| "Find user by username quickly" | Dictionary / map |
| "Store unique values and test membership quickly" | Set |
| "Process tasks in order they arrive" | Queue |
| "Process items in reverse order of insertion" | Stack |
Guideline: Think first about the operations you need (search by key, keep order, uniqueness, FIFO, LIFO). Then pick a data structure that matches those operations.
Example comparison
Suppose you have to store banned user IDs and you frequently check:
"Is user with ID X banned?"
Possible approaches:
- Use a list:
banned = [1, 7, 42]
if user_id in banned:
# may be slow if list is large- Use a set:
banned = {1, 7, 42}
if user_id in banned:
# typically very fast, even for big collectionsFor large numbers of IDs, the set is usually the better choice.
Nested and Combined Data Structures
Real backend applications often use combinations of basic structures.
Examples:
List of dictionaries
A typical JSON-like structure:
users = [
{"id": 1, "username": "alice"},
{"id": 2, "username": "bob"}
]You can loop over them:
for user in users:
print(user["username"])Dictionary of lists
A mapping from category to list of items:
products_by_category = {
"books": ["book1", "book2"],
"movies": ["movie1", "movie2"]
}Dictionary of sets
Track which permissions each role has:
role_permissions = {
"admin": {"read_users", "delete_users"},
"editor": {"read_users", "edit_posts"}
}
if "delete_users" in role_permissions["admin"]:
# allowThis is extremely common in authorization logic.
Understanding basic structures makes such nested combinations much easier to work with.
Summary
- A data structure is a way to organize data so that operations on that data are efficient.
- Lists / arrays store ordered items and are great for index-based access and preserving order.
- Dictionaries / maps store key/value pairs and are ideal for fast lookups by key.
- Sets store unique items and excel at membership checks and mathematical set operations.
- Stacks implement LIFO behavior and are useful for undo, nested operations, and parsing.
- Queues implement FIFO behavior and are fundamental for task processing and background jobs.
- Linked lists represent sequences as chains of nodes; useful conceptually, though less common to implement manually in high-level backend code.
- Real-world backend systems often use nested combinations of these structures.
In later chapters, you will see how to express these structures in Python syntax, and how they are used in web backends, APIs, and database code.
Views: 8
KAHIBARO