Home Development 5 coding exercises that improved my problem-solving skills

5 coding exercises that improved my problem-solving skills

5 coding exercises that improved my problem-solving skills

Dada is a Software Engineer with over 2 years of experience. He has a Bachelor Of Technology (B.Tech) in Computer Science from Bells University of Technology.

He started off as a network administrator, collaborating with teams to solve network and communication challenges. 

Later on, he grew interest in building software tools, and educating friends with what he built through articles and guides.

He currently works as a developer for a fintech company.

During his free time, he loves to read educative content, research on blockchain technology and travel.

I used to hop from one project to another, thinking I’d get better at solving problems with code. But after three years as a freelance software engineer working on backend solutions, I learned that writing more code doesn’t make you a better developer — it requires critical thinking too. Many developers assume they have this skill, not realizing it’s what separates the average developer from solution-driven ones. Here are five exercises I do to improve.

Reverse engineering APIs

This exercise helped me build a mental model of how backend systems work. Imagine working with an API that has unclear documentation, or worse, doesn’t have any at all. This is where you put your system reasoning to the test. I use the Postman API tool to send malformed requests and study the status codes and errors returned in JSON format. With this information, I can easily identify the required fields, validation rules, and the expected request structure.

Another useful tool is your web browser. You can inspect the network tab for more information about the headers, request body and response shape. These two tools alone reveal 90% of what the API is all about.

I retry this process, testing multiple endpoints to map out the API’s external contract. After performing all the needed tests, I make a sequence diagram to visualize what the system looks like.

Performing reverse engineering on third-party APIs can violate terms of service or legal agreements. Practice on API’s that you control, have explicit permission to test, or that are open-source.

Debugging legacy code
Sometimes, old code is not always gold. They can be filled with outdated syntax and redundant code. I recall my first year working on a client’s food delivery app, where I was tasked to audit the backend codebase and propose improvements. I realized the real challenge here wasn’t just about fixing the syntax – it was about understanding the intent of the code.

What is the code meant to do, and what is it not supposed to do? Using this thesis, I uncovered two pricing bugs that were undercharging customers.

If you find it challenging to debug an entire codebase, start small. You can start with the function snippets that are tied to important endpoints.

Refining search algorithms
Choosing a suitable search algorithm becomes a challenge in production scenarios where performance matters.

Now, picture this:

You have a list with millions of users in memory

Each user has a unique ID.

And you want to retrieve a user’s details as fast as possible.

Implementing a linear search algorithm like the one below

func FindUserLinear(users []User, targetID int) *User {
for _, user :=range users {
if user[i].ID==targetID {
return &user[i]
}
}
return nil
}

It is not the best option, because it loops through the record one by one. This algorithm will take more time and resources during execution.

The best algorithm to use here is a hash-based search:

type User struct {
    ID   int
    Name string
}
func BuildUserIndex(users []User) map[int]*User {
index :=make(map[int]*User)

for i :=range users {
index[users[i].ID]=&users[i]
}
return index
}

func FindUserByID(index map[int]User, targetID int) *User {
if user, ok :=index[targetID]; ok {
return user
}
return nil
}

This exercise trains you to make decisions, taking performance, accuracy, and resource efficiency into consideration.

Implementing a small data structure from the start
Rather than using data structure libraries, I build mine from scratch. Along the way, you figure out how data is stored, accessed, and modified under the hood. I personally like this exercise because it makes me think of design decisions, how to handle edge cases, and maintain invariants.

Below is a simple representation of a Least Recently Used (LRU) cache structure, which can be used to save frequently used query results.

// Node represents an item in the LRU cache
type Node struct {
key int
value int
prev *Node
next *Node
}

//Create the cache structure
type LRUCache struct {
capacity int
cache map[int]*Node
head *Node
tail *Node
}

//Create a new LRU cache
func NewLRU(capacity int) *LRUCache {
head :=&Node{}
tail :=&Node{}
head.next=tail
tail.prev=head

return &LRUCache{
capacity: capacity,
cache: make(map[int]*Node),
head: head,
tail: tail,
}
}

Earlier in the year, I worked with my team to troubleshoot a blockchain protocol to spot and report errors. I built an open-source tool called ApexFaucet that automated the request of test tokens and saved the team around 25% of productivity time.

Through developing ApexFaucet, I have noticed a pattern. Building real-world solutions forces a different kind of thinking. Unlike the regular challenges that have specifications and also expected output from you, real-world problems come with uncertainty and incomplete requirements. Learning from that, I now ask myself these four critical questions before developing these tools.

What problem am I solving?

Who is this product for and how will it be used?

What are the constraints I may encounter during development? These could be in terms of time and complexity.

What are the measurable success criteria for this tool?

Whether it’s a basic utility library that provides unique identifiers for records in a database, like Google’s UUID generator, or a full application that manages your finances, the key thing is to create solutions to everyday problems around you.

Any task that tests your mental muscles is a step in the right direction when improving your problem-solving skills. While these code challenges didn’t immediately make me write code faster, they made me better at thinking logically before writing it. And that skill, more than syntax or frameworks, is what compounds over time.

Read whole article here

Leave a Reply

Your email address will not be published.