Most people meet data structures and algorithms (DSA for short) twice. Once at university, where it feels like maths homework. Once in an interview, where someone asks you to reverse a binary tree on a whiteboard and you quietly wonder if you'll ever do that at work.
Then you get the job, and nobody ever says "go use a hash map here." So it's fair to ask: where does this stuff actually get used?
The answer is everywhere, but it's hidden. You don't write class HashMap. You write const seen = {} and you're already using one. The skill isn't memorising algorithms. It's recognising the moment a problem in your project IS one of these classic shapes, then reaching for the right tool instead of brute-forcing it.
Here's where it shows up in real work, with code and real examples. Two quick definitions as we go: a data structure is just how you arrange data in memory (a list, a lookup table, a tree). An algorithm is the steps you run over that data (search it, sort it, find a path).
The hash map: the one you already use every day
A hash map (also called a dictionary, an object, or a Map) stores data as key to value, and it finds any key almost instantly. No matter how big it gets, a lookup stays roughly the same speed. That single property fixes more real bugs than any other structure.
Here's the classic one. You have two lists and you need to match them up. The lazy version loops one inside the other:
// Slow: for every order, scan every user.
// 10k orders x 10k users = 100 million checks.
for (const order of orders) {
const user = users.find(u => u.id === order.userId)
}
That find walks the whole users list every single time. On small data you never notice. On a real dataset it crawls. I hit exactly this on a dashboard that was timing out: the page wasn't slow because of the database, it was slow because of a nested loop like this in the code that built the report.
The fix is to load the users into a hash map once, then look each one up directly:
// Fast: build the lookup once, then each match is instant.
const userById = new Map(users.map(u => [u.id, u]))
for (const order of orders) {
const user = userById.get(order.userId)
}
Same result, but instead of 100 million checks it's about 20 thousand. The page went from timing out to loading in under a second. No new database, no caching layer, just the right data structure.
Where you've seen this in the wild: removing duplicates from a list, counting how many times each word appears, checking if you've "seen this before" in a loop, grouping records by category. Any time you catch yourself writing a loop inside a loop to match things, a hash map is usually the answer. If your slow code is talking to a database instead, the same lesson applies one layer down, which I wrote about in How to optimize SQL queries to run faster.
Stacks and queues: undo buttons and waiting lines
Two structures, one idea: the order things come out.
A stack is last in, first out. The last thing you put on is the first thing you take off, like a stack of plates. A queue is first in, first out, like a line at a coffee shop. First person in line gets served first.
You use a stack every time you hit undo. The editor keeps a stack of your actions. Undo pops the most recent one off the top:
const undoStack = []
function doAction(action) {
applyChange(action)
undoStack.push(action) // newest on top
}
function undo() {
const last = undoStack.pop() // newest comes off first
if (last) revertChange(last)
}
That's the whole undo feature. Ctrl+Z in your editor, the back button in your browser keeping history, even the "call stack" your code runs on are all stacks.
A queue is what runs in the background of almost every serious app. When you upload a video, send a bulk email, or generate a report, the app doesn't make you wait. It drops the job in a queue and a worker picks jobs off the front, one by one, in order. Spotify's "play next" is a queue. So is every print spooler and every job runner like Laravel's queues or a background task system.
The reason this matters: if you ever build something that needs to handle work later instead of right now, you're building a queue, even if a library hides the details.
Trees: autocomplete, file systems, and your database
A tree is data that branches. One thing has children, each child has its own children. Your folders are a tree. A company org chart is a tree. The HTML on this page is a tree (that's literally what the DOM is).
The everyday example most people don't realise is a tree: autocomplete. When you type "rea" and the search box instantly suggests "react", "react native", "reading", that speed comes from a special tree called a trie that stores words by their shared prefixes. All words starting with "rea" sit under the same branch, so the app jumps straight to them instead of scanning a giant list.
The backend example is bigger. Your database uses trees to stay fast. When you put an index on a column, the database builds a B-tree (a wide, shallow tree) behind the scenes. That's why a query with an index returns in milliseconds and the same query without one slows to a crawl on a big table. You're not writing the tree, but every indexed lookup you've ever done rode on one. I dug into the practical side of this in How to optimize SQL queries to run faster, and the trade-offs between SQL and document databases in SQL vs NoSQL: a look at MongoDB and its trade-offs.
Here's a tree you write yourself all the time without naming it: walking a folder structure or a nested menu.
// Count every file in a folder and all its subfolders.
function countFiles(folder) {
let count = folder.files.length
for (const child of folder.subfolders) {
count += countFiles(child) // same function, on each branch
}
return count
}
A function that calls itself to handle each branch is how you deal with any tree. I lean on this constantly. One of my projects is a file explorer for VS Code, and the whole thing is tree-walking: render a folder, render its children, repeat down every branch.
Graphs: maps, social networks, and "people you may know"
A graph is things connected to other things. Points (called nodes) joined by lines (called edges). If a tree is a family with one clear parent per child, a graph is messier: anything can connect to anything, in loops.
The example everyone has used: Google Maps. Every intersection is a node, every road is an edge, and finding the fastest route is a graph algorithm (Dijkstra's, if you want the name) walking outward from your start until it reaches your destination by the cheapest path. Change "distance" to "time with traffic" and it's the same algorithm with different edge weights.
Graphs are also every social network. People are nodes, friendships are edges. "People you may know" is the app looking at friends of your friends, which is just exploring the graph two steps out. The same shape powers Uber matching a rider to nearby drivers and a delivery app planning a route past ten stops.
You meet graphs in plain backend work too. Anything where task A must finish before task B can start is a graph: build pipelines, package dependencies (npm figuring out install order), database migrations that depend on each other. The algorithm that puts them in a safe order is called a topological sort, and a build tool runs it every time you hit deploy.
Sorting and searching: the quiet workhorses
You rarely write a sort by hand. Every language gives you .sort(). But knowing how search behaves changes how you build.
The big one is binary search. If a list is already sorted, you don't scan it from the start. You jump to the middle, see if your target is higher or lower, and throw away half the list. Repeat. A list of a million items is found in about twenty steps instead of a million.
// Find a value in a SORTED array by halving the search each time.
function binarySearch(sorted, target) {
let low = 0, high = sorted.length - 1
while (low <= high) {
const mid = Math.floor((low + high) / 2)
if (sorted[mid] === target) return mid
if (sorted[mid] < target) low = mid + 1 // target is in the top half
else high = mid - 1 // target is in the bottom half
}
return -1
}
This is the same idea behind "git bisect" hunting down the commit that broke your build, and behind a database finding a row in an indexed column. It's also why we keep data sorted in the first place: a sorted list makes search thousands of times faster. When I'm tracking down a bug by halving the possible causes instead of guessing, that's binary search applied to debugging, which is the core of my repro-first method for fixing any bug.
How to actually know which one to reach for
You don't need to memorise every algorithm. You need to recognise the shape of your problem. Here's the cheat sheet I'd give my past self:
- Matching, counting, de-duping, or "have I seen this?" → hash map
- Undo, history, back button, or anything "most recent first" → stack
- Background jobs, waiting lines, "do this later in order" → queue
- Folders, nested menus, anything branching, anything with an index → tree
- Maps, networks, "what connects to what", task ordering → graph
- Fast lookups in sorted data, narrowing down a cause → binary search
The payoff isn't passing interviews. It's that when a page times out or a feature feels sluggish, you know whether you've got a database problem, a network problem, or just the wrong data structure quietly doing a million extra checks. That last one is the most common, and the cheapest to fix. Knowing DSA is mostly knowing it when you see it. If you want the broader set of fundamentals worth learning right now, I covered those in Learn these 10 AI concepts before it's too late, and the architecture side in API design and architecture.
Frequently asked questions
Do I really need data structures and algorithms for everyday web development?
You use them every day, usually without naming them. Objects and Maps are hash maps, arrays you push and pop are stacks, the DOM and your folder tree are trees. You rarely implement them from scratch, but knowing which one fits a problem is what separates code that scales from code that times out on real data.
What's the most useful data structure to learn first? The hash map. It's the one that fixes the most real performance problems, it's built into every language (objects in JavaScript, dicts in Python, maps in Go), and it turns slow nested loops into instant lookups. If you only deeply learn one structure, learn this one.
Where are algorithms used in real apps, not just interviews? Everywhere under the surface. Google Maps uses graph pathfinding, autocomplete uses a tree called a trie, databases use B-trees for indexes and binary search to find rows, background jobs run on queues, and undo buttons run on stacks. The app hides the algorithm, but it's doing the work.
Why does my code get slow as my data grows even though it worked in testing? Almost always a nested loop: code that scans an entire list inside another loop, so the work grows with the square of your data. It's invisible on ten test records and brutal on ten thousand real ones. Swapping the inner scan for a hash map lookup is the most common fix.
Is it worth learning DSA if AI can write the code for me? Yes, because someone still has to know when the AI's answer is the slow one. AI will happily write a working nested loop that falls apart at scale. Recognising that, and asking for the hash-map version, is exactly the judgement that keeps you valuable. The fundamentals are how you review what the AI produces.
What to do now
Don't go grind a hundred algorithm puzzles. Instead, open a project you've already built and look for one nested loop, one .find() inside a for, one place that gets slow with more data. Rewrite that one spot with a hash map and watch it speed up. That single exercise teaches more than a week of theory, because you'll feel the difference on your own code.
That's the real use of DSA: not the whiteboard, but the moment your own project gets faster because you finally spotted the shape of the problem.