← HomeSee All Blogs

Using Data Structures to Optimize Real Backend APIs

2025-12-23

Introduction

For a long time, I treated data structures as “interview-only knowledge.”

Hash tables? Cool for LeetCode.
Big-O? Sure, important… somewhere else.

But while building backend APIs with Express, something clicked:
I was already using data structures every day — I just wasn’t thinking about them intentionally.

This post is about that realization: how classic data structures (especially hash tables) show up in real backend APIs, and how using them on purpose can make your code faster, cleaner, and way more scalable.


The Naive API (Where I Started)

Let’s say you have a simple Express endpoint:

app.get("/users/:id", async (req, res) => {
    const users = await getAllUsersFromDB();
    const user = users.find(u => u.id === req.params.id);
    res.json(user);
});

This works.
It’s readable.
And honestly? I wrote code like this all the time early on.

But look closely:

This is where data structures sneak in.


Hash Tables Are Already Here (You Just Don’t Notice)

In JavaScript, objects and Map are hash tables.

That means this:

const userMap = new Map<string, User>();

isn’t some academic thing — it’s a constant-time lookup machine.

Instead of scanning arrays, you can do this:

const userMap = new Map(users.map(user => [user.id, user]));
const user = userMap.get(req.params.id);

Boom.
O(1) lookups.

Same data.
Different mindset.


Real Example: Simple In-Memory Caching

This was my “ohhh” moment.

Imagine an expensive DB query:

const cache = new Map<string, any>();

app.get("/users/:id", async (req, res) => {
    const id = req.params.id;

    if (cache.has(id)) {
        return res.json(cache.get(id));
    }

    const user = await prisma.user.findUnique({ where: { id } });
    cache.set(id, user);

    res.json(user);
});

What’s happening here?

Suddenly:

This is not theory — this is production behavior.


Another One: Rate Limiting With a Map

Rate limiting used to sound scary to me.
Turns out, it’s just… a hash table.

const requests = new Map<string, number>();

app.use((req, res, next) => {
    const ip = req.ip;
    const count = requests.get(ip) || 0;

    if (count > 100) {
        return res.status(429).send("Too many requests");
    }

    requests.set(ip, count + 1);
    next();
});

Again:

This is a classic interview problem — but it’s also very real backend code.


Where Algorithms Actually Matter in APIs

Here’s what changed my thinking:

Algorithms aren’t about showing off.
They’re about avoiding unnecessary work.

Some real examples:

You don’t need to force them.
You just need to recognize when your code is doing more work than necessary.


When NOT to Overthink It

Important lesson I learned the hard way:

Don’t optimize before it hurts.

Sometimes:

But once something runs:

That’s when data structures stop being “academic” and start being tools.


My Biggest Takeaways

The biggest shift wasn’t learning new syntax — it was learning to ask:

“What am I repeatedly doing that I don’t need to?”


Final Thoughts

Learning algorithms used to feel disconnected from real-world coding.

Now, every time I write an API, I catch myself thinking:

That’s when it all started to feel unified —
CS fundamentals, backend architecture, and real applications all clicking together.

And honestly?
That’s when coding started feeling powerful.

You may also like