
Overcoming the initial struggle and finding a structured way to tackle LeetCode

A comprehensive look at how I prepared for the more holistic parts of software engineering interviews, including system design, reviewing CV projects, and acing take-home assignments.
Cái nhìn toàn diện về cách tôi chuẩn bị cho các phần đánh giá tổng thể hơn trong phỏng vấn kỹ sư phần mềm, bao gồm thiết kế hệ thống, xem xét dự án trong CV, và hoàn thành các bài test lập trình.
A hiring-focused case study of how I translated fragmented warranty evidence, business rules, and human control into Servexa's product and engineering boundaries.
Preparing for algorithmic coding interviews is often described as one of the most stressful parts of securing a software engineering role. When I first started, staring at a blank editor while a timer ticked down felt paralyzing. This post documents my journey from struggling with basic algorithms to finding a structured approach that actually worked.
My starting point was likely the same as many others: opening LeetCode, sorting by "Easy," and immediately feeling overwhelmed by how difficult the "Easy" problems seemed.
The biggest initial struggle wasn't necessarily the syntax of the programming language, but rather the sheer volume of patterns to memorize and the paralyzing fear of "what if I see something I've never encountered before?". I spent hours looking at solutions, nodding along, and then completely forgetting how to solve the same problem a week later. I realized that a significant mindset shift was required: I needed to stop memorizing solutions and start understanding patterns.
The turning point in my preparation was abandoning the "random problem of the day" approach and adopting a structured strategy:
There is a lot of noise online, but these specific resources were the turning points in my preparation:
One of the most useful shifts in my preparation was learning to treat Big O as a way to describe how an implementation scales, not as a list of labels to memorize.
When I first encountered complexity analysis, I tended to look at code and count loops. That works for simple cases, but it breaks down quickly. A better approach is to ask a few structural questions:
O(n), I define what n actually represents.For example, this loop is linear because the body runs once for every element:
But this nested loop grows quadratically:
A more interesting example is binary search. It is not O(log n) because it "looks like" a divide-and-conquer algorithm. It is O(log n) because each step eliminates roughly half of the remaining search space.
That way of thinking changed how I approach optimization. Instead of asking, "Which trick makes this faster?", I now ask, "Where is the bottleneck, and what data structure or algorithm changes the growth rate of that bottleneck?"
This also made Big O feel more practical. It became a language for engineering trade-offs rather than an interview-specific formula sheet.
Another major improvement came from stopping at the API surface and asking what data structures actually look like in memory.
An array is powerful because its elements are stored in a contiguous region of memory. Given a base address and an index, the program can calculate where an element lives directly. That is the intuition behind constant-time indexed access.
A linked list makes a different trade-off. Its nodes do not need to live next to each other. Each node stores a value and a reference to the next node. This makes local insertion cheap when I already have the correct node, but random access is expensive because the program has to follow references one by one.
A hash table uses a hash function to transform a key into a bucket location. That is why membership checks and key-based lookups are usually close to constant time on average, while collisions and resizing explain why the implementation is more nuanced than simply saying "hash maps are O(1)."
A tree is essentially a set of nodes connected by references. A balanced binary search tree can eliminate a large part of the remaining search space at every step, while a badly skewed tree can degrade toward linked-list behavior.
A heap was especially interesting to me because the logical structure looks like a tree, but it can be stored efficiently inside an array. Parent and child relationships can be derived from indices instead of explicit pointers.
Thinking at this level gave me a stronger mental model for choosing structures during interviews:
The important lesson for me was that complexity is not arbitrary. It often follows directly from how the data is physically organized and what work the machine must perform to access or modify it.
Once I had a better understanding of patterns, complexity, and data structures, I needed a study process that I could actually repeat without burning out.
I now structure my preparation into four layers.
Before solving a large number of problems, I review the concepts that influence almost every interview question:
The goal is not to master every advanced implementation. It is to understand the operations each structure supports, their trade-offs, and what those trade-offs imply for complexity.
Next, I practice recognizing recurring problem shapes instead of treating every LeetCode question as something completely new.
Some of the triggers I actively look for are:
This gives me a starting hypothesis, not an automatic answer. I still verify whether the pattern actually fits the constraints.
For each problem, I try to follow the same interview-style process:
This is slower than immediately typing code, but it trains the exact skill interviews actually measure: structured problem solving under incomplete information.
The final step is explaining solutions without looking at the code.
If I can solve a problem but cannot clearly explain:
then I do not consider the problem fully learned.
That rule has become one of the most valuable parts of my preparation because it moves the focus from recognizing a familiar answer to building reusable reasoning.
The biggest outcome of this process is that coding interview preparation started influencing how I think about everyday engineering work.
When I design a feature now, I am more likely to ask how the data should be represented before reaching for an implementation. When a solution feels slow, I look for the structural bottleneck instead of micro-optimizing individual lines. When I explain code, I try to make the assumptions and trade-offs explicit.
That is why I no longer see interview preparation as a separate world of algorithm puzzles. At its best, it trains a more general habit: understand the data, identify the constraints, choose the right abstraction, and explain the trade-off clearly.
Solving a problem in your room is completely different from solving it while someone is watching you.
I quickly learned that speaking out loud is arguably more important than the code itself. In a real interview, silence is your worst enemy. I started practicing by explaining my thought process to a rubber duck (or an empty room).
When it came to mock interviews (often with friends or platforms like Pramp), the biggest lesson was learning how to handle hints. Initially, I saw a hint as a failure. Eventually, I realized that interviews are collaborative; taking a hint gracefully and incorporating it into the solution is a highly positive signal.
Managing stress during the real thing ultimately came down to trusting my preparation and treating the interviewer as a teammate rather than an adversary. It's a journey, but it's one that fundamentally made me a better problem solver.
for (let i = 0; i < n; i++) {
process(i)
}for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
process(i, j)
}
}