Engineered
Big O Notation

Problem Solving Approach

Master a structured 5-step framework for algorithmic problem solving: understand the problem, explore concrete examples, break it down, solve or simplify, and look back to refactor.

Algorithms and the Problem-Solving Mindset

An algorithm is a step-by-step set of instructions designed to accomplish a specific task or solve a computational problem. It describes a predictable process: what operations to perform, and in what exact order, to reach the desired output from a given input.

Thinking algorithmically means transforming an ambiguous or complex problem into a sequence of clear, manageable actions. The goal is not only to arrive at an answer, but to design a reproducible, verifiable process that can be translated into code.

Core Idea

An algorithm is a repeatable sequence of defined steps used to transform a set of inputs into an expected output.

Why Algorithmic Problem Solving Matters

Developing a structured approach to problem solving is crucial across two major software engineering environments:

  • Day-to-Day Programming: Real-world software systems require translating complex business requirements into robust, bug-free instructions. Breaking tasks down methodically prevents edge-case oversights and leads to maintainable code.
  • Technical Interviews: Interviewers rarely evaluate candidates solely on whether they memorize a final answer. Instead, they assess your communication, how you clarify ambiguous constraints, how you handle roadblocks, and how you evaluate trade-offs.

A disciplined mindset bridges both scenarios: understand the problem, plan the architecture, and verify the execution.


The 5-Step Problem Solving Framework

When faced with a novel coding challenge, jumping immediately into typing code often leads to confusion, messy logic, and missed edge cases. Instead, follow this battle-tested 5-step framework:

Step 1: Understand the Problem

Before writing a single line of code, you must be 100% certain about what the problem is actually asking. Making assumptions too early is the most common reason for submitting incorrect solutions.

The 5-Point Understanding Checklist

  1. Restate the problem: Can you explain the challenge in your own words?
  2. Identify the inputs: What data types and values will the function receive? What is their expected size or range?
  3. Identify the outputs: What should the function return? What data type and format is expected?
  4. Check sufficiency: Do the provided inputs contain enough information to produce the required output?
  5. Label important data: What variable names, labels, and state trackers will be essential?

Golden Rule of Problem Solving

Do not start coding until you can clearly explain the problem, its inputs, and its expected outputs to someone else without hesitation.

Problem-Understanding Template

QuestionWhat to ClarifyExample: Two-Sum Problem
What is the task?Restate the problem simply.Find two numbers in an array that add up to a target sum.
What are the inputs?Data types, sizes, ranges.An array of integers (nums) and an integer (target).
What is the output?Return value and structure.An array of indices [index1, index2] or boolean.
Is information sufficient?Missing parameters or edge scenarios.What if no pair adds up to the target? Can elements be reused?
What data matters?Key labels and data structures.Current number, target complement (target - num), index lookup map.

Step 2: Explore Concrete Examples

Concrete examples ground abstract problem descriptions into tangible test cases. They help uncover unspoken requirements, expose edge cases early, and serve as automated sanity checks once you finish coding.

The 4 Essential Example Types

Example CategoryPurposeWhat It Exposes
1. Simple ExamplesEstablish baseline behaviorValidates the standard, happy-path input.
2. Complex ExamplesTest multi-rule interactionsValidates combinations of special characters, large inputs, or unordered data.
3. Empty / Zero InputsTest boundary conditionsClarifies whether empty strings, [], 0, or null return null, 0, or empty collections.
4. Invalid InputsTest error handlingDefines what happens when arguments are wrong types, negative, or undefined.

Case Study: Character Count Function

Consider the challenge: "Write a function that takes a string and returns a count of each alphanumeric character in the string."

Let us construct the 4 test cases before writing any implementation:

Practical Check

Always write down at least one simple, one complex, and one boundary/empty test case. Use them as manual walkthrough traces while developing your algorithm.


Step 3: Break the Problem Down

Once you understand the requirements and edge cases, formulate a step-by-step roadmap. Translating the solution into pseudocode or structured comments bridges the gap between mental concepts and syntax.

Why Pseudocode is Essential

  • Prevents Mental Overload: Separates algorithmic logic from language-specific syntax errors.
  • Identifies Blind Spots: Shows precisely where your logic is vague or incomplete before you spend time debugging code.
  • Demonstrates Structured Thinking: In an interview setting, writing pseudocode first communicates your approach clearly to the interviewer.

Pseudocode Template for charCount


Step 4: Solve or Simplify

When dealing with challenging problems, you may know how to solve 80% of the problem, but feel stuck on a difficult 20% (for instance, complex string parsing or regex).

Do not let the hardest part freeze your progress. Follow the 5-stage simplifying workflow:

StageAction
1. UnderstandIdentify the parts you know how to build immediately.
2. ImplementCode the parts you are confident in first.
3. Set AsideTemporarily bypass the complex obstacle with a placeholder or simple fallback.
4. SimplifyVerify that the partial solution functions correctly on basic inputs.
5. CompleteReturn to the bypassed obstacle with fresh focus and implement the final piece.

Partial Solutions are Valuable

Building a working partial solution establishes momentum and creates a concrete foundation to test the remaining challenging edge cases.

Initial Working Implementation

Here is our first working draft of charCount, solving the core counting logic with standard language tools:

function charCount(str) {
  // 1. Create an object to store counts
  const result = {};

  // 2. Loop over every character
  for (let i = 0; i < str.length; i++) {
    const char = str[i].toLowerCase();

    // Check if character is alphanumeric using regex
    if (/[a-z0-9]/.test(char)) {
      if (result[char] > 0) {
        result[char]++;
      } else {
        result[char] = 1;
      }
    }
  }

  // 3. Return final counts
  return result;
}

// Verification
console.log(charCount("Hello World 123!"));
// { h: 1, e: 1, l: 3, o: 2, w: 1, r: 1, d: 1, '1': 1, '2': 1, '3': 1 }

Step 5: Look Back and Refactor

Getting a working solution is a huge milestone, but it is not the final step. Great engineers review their working code to improve performance, readability, style, and maintainability.

The 6 Questions of Refactoring

Review DimensionQuestions to Ask Yourself
CorrectnessDoes the code handle all edge cases identified in Step 2 (empty inputs, mixed casings)?
ClarityAre variable names intuitive? Is the structure easy to follow at a glance?
PerformanceAre we performing redundant operations (e.g., regex inside tight loops)? What is the Big O?
Style & IdiomsDoes it follow modern conventions (e.g., for...of, nullish coalescing, helper methods)?
ReusabilityWould an extracted helper function make other parts of the system cleaner?
AlternativesWould a hash map, two pointers, or built-in utilities be cleaner or faster?

Refactoring in Action: Optimizing charCount

In JavaScript, executing a regular expression test (/[a-z0-9]/.test(char)) on every character in a long string introduces significant overhead compared to direct character code arithmetic.

Let us refactor both implementations for maximum speed, readability, and idiomatic style:

/**
 * Efficient helper checking if a character is alphanumeric via ASCII codes.
 * Significantly faster than evaluating a regular expression in a loop.
 */
function isAlphanumeric(code) {
  return (
    (code > 47 && code < 58) || // numeric (0-9)
    (code > 64 && code < 91) || // upper alpha (A-Z)
    (code > 96 && code < 123)   // lower alpha (a-z)
  );
}

/**
 * Refactored charCount:
 * - Time Complexity: O(n) linear scan
 * - Space Complexity: O(k) where k is the character set size (O(1) auxiliary fixed space)
 */
function charCount(str) {
  if (typeof str !== "string") return {};

  const result = {};

  for (let i = 0; i < str.length; i++) {
    const code = str.charCodeAt(i);
    if (isAlphanumeric(code)) {
      const char = str[i].toLowerCase();
      result[char] = (result[char] || 0) + 1;
    }
  }

  return result;
}

Key Takeaways

  • Don't Jump to Code: Always spend the first minutes understanding requirements, inputs, outputs, and constraints.
  • Test Early with Concrete Cases: Write out simple, complex, empty, and invalid inputs to serve as test cases before you begin implementation.
  • Pseudocode First: Blueprint your logic into simple steps. This clarifies uncertain spots before coding.
  • Simplify When Stuck: Solve the sub-problems you know how to do. Build momentum and isolate the hard parts.
  • Refactor with Purpose: Evaluate your working solution for Time Complexity, Space Complexity, Clarity, and Idiomatic Style.

How is this lesson?