DEV Community

Cover image for Is Learning DSA Boring? Let's Use DSA View View πŸ‘€πŸ‘€ (Two Sum, Binary Search, and Bubble Sort)
nyaomaru
nyaomaru

Posted on

Is Learning DSA Boring? Let's Use DSA View View πŸ‘€πŸ‘€ (Two Sum, Binary Search, and Bubble Sort)

Visualizing execution logic changes mental models

Hoi hoi!

I’m @nyaomaru, a frontend engineer who dislikes crowded places, so I'm planning to take a quiet vacation in September. 🏝️

Have you used DSA View View already? πŸ‘€πŸ‘€

DSA View View allows you to understand DSA by visualizing how your implementation actually runs.

But just introducing the tool is not enough.

Can it actually help us understand DSA?

In this article, we'll walk through three classic problems:

  • Two Sum
  • Binary Search
  • Bubble Sort

We'll first understand the algorithm, and then see what is actually happening with DSA View View.

Let's learn together! 😸


πŸ—ΊοΈ Two Sum

Let's start with a very famous problem.

Given an array of numbers and a target value, find the indices of two numbers whose sum equals the target.

For example,

nums = [2, 7, 11, 15];
target = 9;
Enter fullscreen mode Exit fullscreen mode

The answer is

[0, 1];
Enter fullscreen mode Exit fullscreen mode

Because

2 + 7 = 9
Enter fullscreen mode Exit fullscreen mode

Simple!

So, how should we find them? πŸ€”

Brute Force

The easiest approach is probably checking every possible pair.

function twoSum(nums: number[], target: number): number[] {
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[i] + nums[j] === target) {
        return [i, j];
      }
    }
  }

  return [];
}
Enter fullscreen mode Exit fullscreen mode

This works.

But if the array becomes large, we may need to compare a lot of pairs, right?

The time complexity is O(nΒ²)

Can we avoid checking the same values again and again?

Yes.

Let's use a Map.

function twoSum(nums: number[], target: number): number[] {
  const seen = new Map<number, number>();

  for (let i = 0; i < nums.length; i++) {
    const current = nums[i];
    const need = target - current;

    if (seen.has(need)) {
      return [seen.get(need)!, i];
    }

    seen.set(current, i);
  }

  return [];
}
Enter fullscreen mode Exit fullscreen mode

The important part is this πŸ‘‡

const need = target - current;
Enter fullscreen mode Exit fullscreen mode

Instead of asking

Which two numbers should I combine?

we ask

What number do I need to complete the target?

Let's follow the example.

At first

current = 2
target = 9

need = 9 - 2
     = 7
Enter fullscreen mode Exit fullscreen mode

Have we already seen 7?

No.

So we remember 2.

seen = {
  2 β†’ 0
}
Enter fullscreen mode Exit fullscreen mode

Next

current = 7
target = 9

need = 9 - 7
     = 2
Enter fullscreen mode Exit fullscreen mode

Have we already seen 2?

Yes! πŸ‘€πŸ‘€

seen = {
  2 β†’ 0
}
Enter fullscreen mode Exit fullscreen mode

So

return [0, 1];
Enter fullscreen mode Exit fullscreen mode

Done!

Because we only need to walk through the array once, the time complexity becomes.

Time:  O(n)
Space: O(n)
Enter fullscreen mode Exit fullscreen mode

πŸ‘€ Let's View View It

The implementation is quite small.

But when I was first learning this pattern, this part still felt a little magical.

if (seen.has(need))
Enter fullscreen mode Exit fullscreen mode
  • Where did need come from?
  • What is inside seen at this moment?
  • Why does checking the previous values solve the problem?

This is exactly where visualization helps.

With DSA View View, we can move through the runtime one step at a time and inspect how the values change.

2
↓
Need 7
↓
Remember 2
↓
7
↓
Need 2
↓
Found 2!
πŸŽ‰
Enter fullscreen mode Exit fullscreen mode

Now the Map is not just some mysterious trick.

We can actually follow the idea.

Remember what we have already seen, and check whether the value we need is there.

Nice! 😸


πŸ” Binary Search

Next is Binary Search.

Suppose we have this sorted array

[1, 3, 5, 7, 9, 11, 13]
Enter fullscreen mode Exit fullscreen mode

And we want to find

11
Enter fullscreen mode Exit fullscreen mode

Of course, we could start from 1 and check every number.

1 β†’ 3 β†’ 5 β†’ 7 β†’ 9 β†’ 11
Enter fullscreen mode Exit fullscreen mode

That works.

But Binary Search does something smarter.

Instead of checking from the beginning, it checks the middle.

[1, 3, 5, 7, 9, 11, 13]
          ↑
         mid
Enter fullscreen mode Exit fullscreen mode

Our middle value is 7.

We are looking for 11.

11 > 7
Enter fullscreen mode Exit fullscreen mode

Because the array is sorted, we already know something very useful.

Everything on the left side of 7 is also smaller than 11.

So, we don't need that half anymore. πŸ‘‹

[1, 3, 5, 7, 9, 11, 13]
             β””β”€β”€β”€β”€β”€β”€β”€β”˜
               search
Enter fullscreen mode Exit fullscreen mode

Now we check the middle of the remaining range.

[9, 11, 13]
     ↑
    mid
Enter fullscreen mode Exit fullscreen mode

And

11 === 11
Enter fullscreen mode Exit fullscreen mode

Found it! πŸŽ‰

Here is the implementation.

function binarySearch(nums: number[], target: number): number {
  let left = 0;
  let right = nums.length - 1;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);

    if (nums[mid] === target) {
      return mid;
    }

    if (nums[mid] < target) {
      left = mid + 1;
    } else {
      right = mid - 1;
    }
  }

  return -1;
}
Enter fullscreen mode Exit fullscreen mode

There are three important variables.

left
right
mid
Enter fullscreen mode Exit fullscreen mode

They represent the current search range.

For our example, they start like this

left = 0
right = 6
mid = 3

[1, 3, 5, 7, 9, 11, 13]
 ↑        ↑          ↑
left     mid       right
Enter fullscreen mode Exit fullscreen mode

Because

nums[mid] < target;
Enter fullscreen mode Exit fullscreen mode

we move left.

left = mid + 1;
Enter fullscreen mode Exit fullscreen mode

Now

[1, 3, 5, 7, 9, 11, 13]
             ↑   ↑   ↑
           left mid right
Enter fullscreen mode Exit fullscreen mode

And 11 is found.

Why Is Binary Search Fast?

This is the interesting part.

Each step removes about half of the remaining candidates.

If there are 1,000 values, we don't necessarily need 1,000 checks.

It becomes roughly

1000
↓
500
↓
250
↓
125
↓
...
Enter fullscreen mode Exit fullscreen mode

That's why Binary Search has

Time:  O(log n)
Space: O(1)
Enter fullscreen mode Exit fullscreen mode

But there is one very important condition.

The data must be sorted.

Without sorted data, we cannot safely throw away half of the search range.

πŸ‘€ Let's View View It

Binary Search is one of the algorithms that made me want a visualization tool in the first place.

The code itself is short

left = mid + 1;
Enter fullscreen mode Exit fullscreen mode

or

right = mid - 1;
Enter fullscreen mode Exit fullscreen mode

Easy.

But when learning it, I sometimes found myself thinking:

Wait... which part are we searching now? 😿

When we visualize left, mid, and right, the idea becomes much easier to follow.

We are not randomly changing three numbers.

We are continuously shrinking the search area.

β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

       ↓

        β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

       ↓

          β–ˆβ–ˆβ–ˆ

       ↓

           β–ˆ
Enter fullscreen mode Exit fullscreen mode

That's Binary Search!

Cut the unnecessary half.

Then cut it again. And again. And again.

Until we find the answer. βœ‚οΈπŸ˜Έ


🫧 Bubble Sort

Finally, let's sort something!

Consider this array.

[5, 1, 4, 2, 8];
Enter fullscreen mode Exit fullscreen mode

We want.

[1, 2, 4, 5, 8];
Enter fullscreen mode Exit fullscreen mode

Bubble Sort repeatedly compares two neighboring values.

If they are in the wrong order, it swaps them.

Let's look at the beginning.

[5, 1, 4, 2, 8]
 ↑  ↑
Enter fullscreen mode Exit fullscreen mode

Compare

5 > 1
Enter fullscreen mode Exit fullscreen mode

So swap them.

[1, 5, 4, 2, 8]
Enter fullscreen mode Exit fullscreen mode

Next

[1, 5, 4, 2, 8]
    ↑  ↑
Enter fullscreen mode Exit fullscreen mode

Again

5 > 4
Enter fullscreen mode Exit fullscreen mode

Swap!

[1, 4, 5, 2, 8]
Enter fullscreen mode Exit fullscreen mode

And continue.

[1, 4, 5, 2, 8]
       ↑  ↑

5 > 2
Enter fullscreen mode Exit fullscreen mode

Swap!

[1, 4, 2, 5, 8]
Enter fullscreen mode Exit fullscreen mode

Eventually, larger values move toward the end of the array.

They kind of...

bubble up. 🫧

That's why it is called Bubble Sort.

Here is a simple implementation:

function bubbleSort(nums: number[]): number[] {
  for (let i = 0; i < nums.length - 1; i++) {
    for (let j = 0; j < nums.length - i - 1; j++) {
      if (nums[j] > nums[j + 1]) {
        [nums[j], nums[j + 1]] = [nums[j + 1], nums[j]];
      }
    }
  }

  return nums;
}
Enter fullscreen mode Exit fullscreen mode

We repeatedly compare

nums[j];
Enter fullscreen mode Exit fullscreen mode

And

nums[j + 1];
Enter fullscreen mode Exit fullscreen mode

and swap them when necessary.

After one full pass, the largest remaining value reaches its correct position near the end.

So on the next pass, we don't need to check that position again.

That's why the inner loop contains.

nums.length - i - 1;
Enter fullscreen mode Exit fullscreen mode

Complexity

Bubble Sort isn't very fast for large arrays.

Its time complexity is

Time:  O(nΒ²)
Space: O(1)
Enter fullscreen mode Exit fullscreen mode

So I probably won't suddenly replace production sorting with Bubble Sort tomorrow. 😸

But as a learning example, I really like it.

Why?

Because you can see the algorithm working.

πŸ‘€ Let's View View It

This is probably the most visually satisfying one of the three.

Instead of only reading

[nums[j], nums[j + 1]] = [nums[j + 1], nums[j]];
Enter fullscreen mode Exit fullscreen mode

we can follow the values moving through the array.

[5, 1, 4, 2, 8]

 ↓ swap

[1, 5, 4, 2, 8]

    ↓ swap

[1, 4, 5, 2, 8]

       ↓ swap

[1, 4, 2, 5, 8]
Enter fullscreen mode Exit fullscreen mode

Then another pass begins.

The code contains nested loops, indexes, comparisons, and swaps.

But visually, the basic rule is extremely simple:

Compare neighbors. If the left one is bigger, swap them.

Repeat. Repeat. Repeat.

Sorted! πŸŽ‰


🧠 What Did We Actually Learn?

These three problems look quite different.

But each one introduces a useful way of thinking.

Two Sum

Remember information from previous steps.

Have I already seen what I need?
Enter fullscreen mode Exit fullscreen mode

Binary Search

Use what we already know to remove impossible candidates.

Can I safely discard half of the search space?
Enter fullscreen mode Exit fullscreen mode

Bubble Sort

Break a larger problem into many small comparisons.

Are these two values in the correct order?
Enter fullscreen mode Exit fullscreen mode

This is one of the things I find interesting about learning DSA.

At first, the implementation can look like a collection of indexes, loops, conditions, and mysterious variables.

But behind the code, there is usually a much simpler idea.

And sometimes I don't fully understand that idea just by staring at the code.

I want to view it. πŸ‘€πŸ‘€


🎯 Conclusion

In this article, we looked at three classic algorithms:

  • Two Sum with a Map
  • Binary Search
  • Bubble Sort

And more importantly, we looked at how the data changes while they run.

I think this is where visualization can be especially useful.

  • Reading the final implementation tells us what the code is.
  • Stepping through it helps us understand why it works.

That's exactly why I built DSA View View.

You can write or load a TypeScript implementation, run it with your own inputs, and move backward and forward through the runtime.

If you are learning DSA too, try taking a problem you already solved and viewing it step by step.

You may notice something you didn't notice when only reading the code. πŸ‘€

And if there is a DSA problem you want me to cover next, please let me know in the comments!

I still have many algorithms to learn myself. 😸

Let's train our DSA muscles together! πŸ’ͺ

If you like DSA View View, please give it a star ⭐

GitHub logo nyaomaru / dsa-view-view

DSA View View allows you to understand DSA to see the data flow. πŸ‘€πŸ‘€ Of course, it's free.

DSA View View

DSA View View

DSA View View turns TypeScript algorithm functions into step-by-step visual stories. πŸ‘€πŸ‘€

DSA View View TV

Write code, run it with structured inputs, and see the arrays, matrices trees, lists, stacks, pointers, and return values move as the function executes.

It is built for those moments when reading the code is not enough and you want to view why the answer changes.

DSA View View demo

Why Try It?

  • 🧠 Step through real TypeScript
    Paste or edit a function, validate it, then run the exact code in the browser.

  • 🧩 Views that match the data
    Arrays become bars, matrices become grids, trees become node graphs, linked lists become chains, and two-pointer area problems get their own visual view.

  • 🌳 DSA-friendly inputs out of the box
    TreeNode, ListNode, MinHeap, MaxHeap, PriorityQueue, nested arrays matrices, strings, numbers, and class-style inputs are supported without ceremony.

  • πŸ”Ž 39 built-in examples
    Search by name, browse…

And my DSA View View has launched at TinyLaunch! πŸš€ Please take a loot πŸ‘‡

See you in the next article!

Top comments (9)

Collapse
 
ofri-peretz profile image
Ofri Peretz

The reframe from "which two numbers combine?" to "what do I need right now?" is the actual insight worth internalizing, and it transfers well beyond Two Sum β€” you see the same shift in a lot of graph traversal problems where caching visited nodes changes the question from "have I found it?" to "have I been here before?". What I've noticed on engineering teams is that developers who get the LLM to write the O(n) version often can't explain why it works, and when the interviewer or code reviewer asks them to trace it by hand they're stuck. A tool that forces you to step through seen at each iteration closes that gap in a way that just reading the solution never does.

Collapse
 
nyaomaru profile image
nyaomaru

That’s a great insight. 😺

Reframing the question is a transferable skill that goes far beyond DSA and applies to everyday engineering problems as well.

AI can generate working code, but without understanding why it works, debugging and maintaining it becomes much harder. In the AI era, being able to trace and explain our reasoning may become more important than ever.

Collapse
 
ofri-peretz profile image
Ofri Peretz

The debugging point holds, and there's a concrete mechanism behind it. Someone who cannot reconstruct why a two-pointer approach works for Two Sum on a sorted array will reach for the same pattern when the array is unsorted, or when duplicates are allowed β€” and get silent wrong answers instead of an error. The model optimized for "passes the given examples," not "handles the invariant." That gap is exactly what reasoning closes: not as an abstract exercise, but as the thing that lets you change one constraint without breaking everything else.

Thread Thread
 
nyaomaru profile image
nyaomaru

That β€œhandles the invariant” point is exactly what scares me. 😹

I feel like training our DSA muscles gives us a better chance of dealing with uncertainty when constraints change, instead of only knowing how to make one example pass.

Of course, DSA alone is not enough. But I do think strong fundamentals remain important no matter how much the tools around us change.

That’s one of the reasons I want to keep learning it too. πŸ’ͺ😸

Thread Thread
 
ofri-peretz profile image
Ofri Peretz

You're right that the transfer value is real. The failure mode I've hit most often is not that someone can't recognize a problem as "binary search shaped" β€” it's that they can't articulate the pre-condition that makes binary search valid. When requirements shift mid-project (sorted β†’ partially sorted, sequential β†’ concurrent), that articulation is exactly what tells you whether your solution still holds or needs to be rebuilt from scratch. The people who can name the invariant are the ones who catch the breakage in review instead of production.

Thread Thread
 
nyaomaru profile image
nyaomaru

I think that’s exactly where a lot of the difficulty of software engineering lives.

We don’t just build something once and finish. The system keeps changing, the requirements keep changing, and assumptions that were correct at one point can become wrong later.

That constant need to adapt is what makes software engineering both interesting and difficult.

In production, I think what we’re really looking for is not just β€œa correct answer,” but the right answer inside all of that changing complexity.

And honestly, I find that difficult all the time too. 😹

Collapse
 
alexshev profile image
Alex Shev

This is a strong reminder that dsa, typescript, opensource, learning need an observable contract. The happy path is rarely the expensive part; it is the boundary behavior, stale state, and partial failure path that decide whether the design holds up in production.

Collapse
 
nyaomaru profile image
nyaomaru

Thanks for the thoughtful comment! 😸

Learning DSA doesn’t translate directly into production quality, but I believe the fundamentals help develop the reasoning skills needed to understand edge cases, stale state, and partial failures.

Collapse
 
alexshev profile image
Alex Shev

Exactly. The lasting value is not memorizing a particular solution but learning to state the constraints, test the edge cases, and notice when an apparently simple transformation changes the problem.