Skip to main content

Command Palette

Search for a command to run...

How Algorithmic Thinking Improves Problem Solving

Updated
6 min read
How Algorithmic Thinking Improves Problem Solving
A

I am a backend-focused Software Engineer with 4+ years of experience building scalable APIs, distributed systems, and cloud-native applications in production environments.

Previously, I worked at Morgan Stanley where I designed and optimized Java Spring Boot services powering critical fintech workflows. My work included migrating legacy SOAP services to REST, improving API throughput by 40%, strengthening security across multiple applications, and building CI/CD pipelines that reduced deployment time and production defects.

I enjoy working on backend architecture, database performance, system reliability, and developer productivity. Currently, I am focused on sharpening my data structures, distributed systems knowledge, and building public-facing projects while actively seeking Software Engineer II opportunities.

I write about backend engineering, system design, cloud services, and lessons learned from real-world production systems.

What is an Algorithm?

In the simplest form, an algorithm is a well-defined, finite sequence of unambiguous instructions designed to solve a problem for a given set of inputs.

Understanding how different algorithms to the same problem work

Let's take an example.

Suppose Alice, Bob and Charlie are three Software Engineers working at Foogle. Dan, the Team Lead, asked them to come up with an algorithm to find the factors of a positive number N.

A natural number X is said to be a factor of number N when X completely divides N without leaving any remainder.

All three come up with different solutions to Dan’s problem. Let us understand each approach and compare them based on the number of checks performed and their approximate runtime.

Alice's Algorithm

Alice approaches the problem using a straightforward brute-force method. She declares a variable factors and initializes it to 0. She then iterates through the entire range [1, N] and checks whether each number divides N evenly. If it does, she increments the factor count.

func findFactors(var N) {
    var factors = 0;
    var i = 1;

    while ( i <= N) {
        if (N % i == 0)
            factors = factors + 1;
        i = i + 1;
    }

    return factors;
}

This approach is simple and easy to understand, but it performs a large number of checks, especially for big values of N.

Bob's Algorithm

Bob makes the following observations:

  1. For any number N, both 1 and N are always factors of N.
  2. For any number N, there are no factors strictly between N/2 and N. For example, if N = 24, no number between 13 and 23 divides 24 evenly.

Based on this, Bob initializes factors to 2 and iterates only through the range [2, N/2]

func findFactors(var N) {
    var factors = 2;
    var i = 2;

    while ( i <= N/2) {
        if (N % i == 0)
            factors = factors + 1;
        i = i + 1;
    }

    return factors;
}

Bob’s algorithm performs fewer checks than Alice’s approach. However, as the value of N increases, the total number of checks Bob performs still increases steadily with N. While this optimization improves performance in practice, it does not fundamentally change how the amount of work grows as the input becomes larger.

Charlie's Algorithm

While Alice and Bob focused on reducing unnecessary checks, Charlie approached the problem from a different angle. Instead of checking fewer numbers within the same range, Charlie looked for a mathematical property that could significantly reduce the range itself.

He observed that for any number N, there exists a pair of numbers A and B such that: A x B = N.

By the associative property of multiplication, B × A = N also holds.

The associative property states that the way numbers are grouped in a multiplication operation does not change the product.

Charlie used this insight to design an algorithm that only checks values of A up to the square root of N. Whenever A divides N evenly, B = N / A automatically becomes another factor.

However, Charlie also accounted for a special case. If A × A = N, then N is a perfect square and the factor A should only be counted once.

Example 1: When N = 24 (Not a perfect square)

A x B = NB x A = NFactors
01 x 24 = 2424 x 01 = 240 + 2 = 2
02 x 12 = 2412 x 02 = 242 + 2 = 4
03 x 08 = 2408 x 03 = 244 + 2 = 6
04 x 06 = 2406 x 04 = 246 + 2 = 8

Example 2: When N = 100 (Perfect square)

A x B = NB x A = NFactors
01 x 100 = 100100 x 01 = 1000 + 2 = 2
02 x 50 = 10050 x 02 = 1002 + 2 = 4
04 x 25 = 10025 x 04 = 1004 + 2 = 6
05 x 20 = 10020 x 05 = 1006 + 2 = 8
10 x 10 = 10010 x 10 = 1006 + 1 = 9
func findFactors(var N) {
    var factors = 0;
    var sqrt = squareRoot(N);
    var i = 1;

    while ( i <= sqrt) {
        if (N % i == 0)
            if (N/i == i)
                factors = factors + 1;
            else
                factors = factors + 2;
        i = i + 1;
    }

    return factors;
}

Unlike the previous approaches, Charlie’s method changes how the number of operations grows as the input increases. This difference becomes especially important for very large values of N. In the next part, we will introduce a formal way to describe and compare this growth using time complexity.

Analysis of the Algorithms

💡
The following estimates are simplified and meant to show how different approaches scale as the input grows, not to represent exact execution times.

Alice, Bob, and Charlie submitted their solutions for review. Dan analyzed each one and chose to merge Charlie’s solution while declining the others.

For comparison, assume a computer with a clock speed of 1 GHz, meaning it can execute approximately 10⁹ instructions per second.

Assume N = 1,000,000,000 (or 10⁹). Then:

  1. Alice's algorithm checks all numbers from 1 to 10⁹. Estimated time = 10⁹ ÷ 10⁹ = 1 second

  2. Bob’s algorithm checks numbers from 1 to 10⁹ ÷ 2. Estimated time = (10⁹ ÷ 2) ÷ 10⁹ = 0.5 seconds

  3. Charlie’s algorithm checks numbers only up to √10⁹. Estimated time ≈ √10⁹ ÷ 10⁹ ≈ 31.62 microseconds

Let's see how each of these algorithms would perform for even larger values of N:

AlgorithmN = 10¹⁰N = 10¹⁵N = 10²⁰
Alice's Algorithm (N iterations)10 seconds10⁶ seconds ≈ 12 days10¹¹ seconds ≈ 3169 years
Bob's Algorithm (N÷2 iterations)5 seconds5 x 10⁵ seconds ≈ 6 days5 x 10¹⁰ seconds ≈ 1585 years
Charlie's Algorithm (√N iterations)0.001 seconds0.0316 seconds10 seconds

Conclusion

In this example, we compared different approaches to solving the same problem and saw why choosing an efficient algorithm matters.

The runtime of a function can depend on many factors, such as the programming language, system architecture, and hardware performance. Because of this, we need ways to compare algorithms independently of the system on which they run. While execution time may vary across machines, the number of steps performed by an algorithm for a given input remains consistent.

In upcoming blogs, we will introduce two widely used metrics for analyzing algorithm efficiency: Time Complexity and Space Complexity.

Stay tuned, and thank you for reading.