Back to blog
← View series: problem solving with dsa
Problem Solving with DSA

~/blog

Flowcharts and Pseudocode

Jun 2, 20268 min readBy Mohammed Vasim
dsapythondata-structuresalgorithmsproblem-solving

The Problem

You write a function to check if a string is a palindrome:

python
def is_palindrome(s):
    return s == s[::-1]

It passes for "racecar". You run it on "A man, a plan, a canal: Panama". It fails. You add s.lower(). It fails again — spaces and punctuation break the comparison. You add filter logic. Then you notice the empty string isn't handled. Then single characters. Between the preprocessing, the edge case guards, and the loop logic, the function that started at six lines has tripled in size, and you have lost confidence that all the paths are correct.

This is the pattern for most problems beyond trivial scope. The instinct to start coding immediately pays off only when the problem fits in your head in one pass. When it involves branching, loops, or multiple edge cases, writing code first produces logic that is correct by coincidence and fragile by construction.

Flowcharts and pseudocode replace reactive patching with deliberate design. They force you to see every branch, every edge case, and every loop exit before you write a line of code.

Flowchart

A flowchart is a diagram of control flow. Each shape has a specific meaning:

  • Oval — start or end. Exactly one start per diagram, at least one end.
  • Rectangle — a process or operation. "max = list[0]", "increment i by 1".
  • Diamond — a decision with exactly two outgoing paths (yes/no, true/false).
  • Parallelogram — input or output. Reading data, printing results.
  • Arrow — direction of flow. Conventionally top-to-bottom or left-to-right.

The diagram for "find the largest number in a list":

mermaid
flowchart TD
    Start([Start]) --> Input[Read list]
    Input --> Init[max = list[0], i = 1]
    Init --> Check{i < len(list)?}
    Check -->|Yes| Compare{list[i] > max?}
    Compare -->|Yes| Update[max = list[i]]
    Update --> Increment[i = i + 1]
    Compare -->|No| Increment
    Increment --> Check
    Check -->|No| Output[Print max]
    Output --> End([End])

Every path is drawn explicitly. The loop back is a visible arrow, not a hidden while keyword. You can trace the three interesting cases — empty list, single element, all equal — by following the arrows.

For a problem with more branching, the flowchart's value becomes more obvious. Here is "check if a number is prime":

mermaid
flowchart TD
    Start([Start]) --> Input[Input n]
    Input --> EdgeCheck{n ≤ 1?}
    EdgeCheck -->|Yes| NotPrime[Print "Not prime"]
    EdgeCheck -->|No| Init[i = 2]
    Init --> LoopCheck{i × i ≤ n?}
    LoopCheck -->|No| Prime[Print "Prime"]
    LoopCheck -->|Yes| DivCheck{n % i == 0?}
    DivCheck -->|Yes| NotPrime2[Print "Not prime"]
    DivCheck -->|No| Increment[i = i + 1]
    Increment --> LoopCheck
    NotPrime --> End([End])
    NotPrime2 --> End
    Prime --> End

Three decision diamonds, two possible "not prime" exits (edge case and divisibility), and a loop bounded by i × i ≤ n. The structure is visible at a glance — no hidden else clauses or break statements.

Tracing Through the Diagram

The real test of a flowchart is running edge cases through it manually. Take the find-max diagram against an empty list:

  1. Read list — the list is [].
  2. max = list[0], i = 1 — crash. Accessing index 0 of an empty list is undefined.

The flowchart revealed a missing guard before a single line of implementation. The fix: add a diamond — "Is the list empty?" with a "Return None" exit.

Now trace [5] (single element):

  1. max = 5, i = 1
  2. 1 < 1? — No
  3. Print max — prints 5

The loop condition naturally handles this case. No special branch needed.

Trace [5, 3, 9, 2]:

Stepimaxlist[i]Decision
Init15
Check11 < 4 → Yes
Compare533 > 5 → No
Increment2
Check22 < 4 → Yes
Compare599 > 5 → Yes
Update9
Increment3
Check33 < 4 → Yes
Compare922 > 9 → No
Increment4
Check44 < 4 → No
Output9Print max

The trace is mechanical. Each row follows one arrow. No runtime, no debugger.

The same exercise on the prime checker flowchart reveals a different insight. Trace n = 1:

  1. n ≤ 1? — Yes
  2. Print "Not prime" — correct. 1 is not prime by definition.

Trace n = 2:

  1. n ≤ 1? — No
  2. i = 2
  3. i × i ≤ n?2 × 2 ≤ 2 → No
  4. Print "Prime" — correct.

Trace n = 17:

Stepin % iDecision
Init2
LoopCheck24 ≤ 17 → Yes
DivCheck211 ≠ 0 → No
Increment3
LoopCheck39 ≤ 17 → Yes
DivCheck322 ≠ 0 → No
Increment4
LoopCheck416 ≤ 17 → Yes
DivCheck411 ≠ 0 → No
Increment5
LoopCheck525 ≤ 17 → No
Print"Prime"

The loop runs for i = 2, 3, 4 and stops when i = 5 violates the condition. The algorithm touches four divisors for a number that could have been checked by trial division up to n-1 (16 iterations). The flowchart makes the optimization visible — the i × i ≤ n bound is the reason.

Pseudocode

Pseudocode sits between a flowchart and real code. It borrows the structure of programming languages — conditionals, loops, functions — but uses natural language for the expressions.

The find-max in pseudocode:

FUNCTION find_max(list) IF list is empty THEN RETURN None max = list[0] FOR i = 1 TO list.length - 1 IF list[i] > max THEN max = list[i] RETURN max END FUNCTION

Pseudocode is language-agnostic. The same logic maps to Python, Java, C++, or JavaScript with mechanical changes.

There is a spectrum of how close pseudocode looks to real code. At one end is almost-English, useful when communicating with non-programmers:

Begin Input FirstNumber Input SecondNumber SUM = FirstNumber + SecondNumber Print SUM End

At the other end is almost-code, useful when designing for a specific environment but not yet committed to syntax:

function find_max(list): if list is empty: return None max = list[0] for i in range(1, len(list)): if list[i] > max: max = list[i] return max

The right level depends on the audience. For a teammate who knows Python and wants to validate the algorithm, the almost-code form is efficient. For a stakeholder who does not read code, the flowchart or almost-English pseudocode is clearer.

Flowchart vs Pseudocode

AspectFlowchartPseudocode
When to useUnderstanding branching logic, communicating with non-programmersDesigning algorithms before writing code
StrengthsVisual, easy to spot missing pathsCompact, closer to actual code
WeaknessBulky for large problemsLess visual — easy to miss branches
Best forProblems with complex conditionalsProblems with sequential logic

They complement each other. For a binary search or a tree traversal, start with a flowchart — the branching structure is the hard part. For a merge, a factorial, or a string transformation, start with pseudocode — the logic is linear and a diagram adds overhead.

Translating to Python

The find-max pseudocode translates directly:

python
def find_max(lst):
    if not lst:
        return None

    max_val = lst[0]
    for i in range(1, len(lst)):
        if lst[i] > max_val:
            max_val = lst[i]

    return max_val

The translation is mechanical because the algorithm's structure was already decided in pseudocode. The Python-specific choices are None for absence, range() for iteration, and not lst for the empty check.

The same mechanical mapping works for the prime checker:

python
def is_prime(n):
    if n <= 1:
        return False

    i = 2
    while i * i <= n:
        if n % i == 0:
            return False
        i += 1

    return True

The pseudocode used × for multiplication; Python uses *. The pseudocode used %; Python uses the same operator. Every structural decision — the early return on n ≤ 1, the loop bound, the default True at the end — was made before writing Python. The code writes itself once the design is done.

Common Mistakes

Mistake 1 — A decision diamond with an undefined branch. Every diamond must have exactly two outgoing paths. In the prime checker flowchart, both n % i == 0 cases — true and false — are drawn. If only the true branch were drawn and the false branch implicitly fell through to the next diamond, the diagram would be incomplete and the logic ambiguous. When a branch should do nothing, draw it explicitly: a short arrow to the next node or a box labeled "continue".

Mistake 2 — Pseudocode that depends on language-specific features. Negative indexing (list[-1]), implicit truthiness (if list), and mutable default arguments are Python-isms. Writing them in pseudocode assumes the reader and implementer share those conventions. Keep pseudocode to operations available in every language: indexing by position, explicit comparisons, explicit null checks.

Mistake 3 — Pseudocode detailed enough to be real code. If your pseudocode reads like Python with different variable names, you skipped the design step. The purpose is to think about the algorithm, not to pre-write the implementation. Write "for every second element" instead of for i in range(0, len(arr), 2). Save the exact syntax for when you are writing real code.

Mistake 4 — Not updating the diagram when the algorithm changes. A flowchart or pseudocode that contradicts the implementation is worse than none at all — it misleads the next reader. If you discover a missing edge case during implementation, update the diagram. If the diagram lives in a doc or a whiteboard, the cost of keeping it in sync is negligible compared to the cost of debugging against stale documentation.

When You Can Skip This

If you sketch on paper or in your head before typing, you are already doing what flowcharts and pseudocode formalize. The notation itself is overhead — it pays off when the problem has more than three decision points or when the loop structure has multiple exit conditions.

The useful habit is not drawing perfect diagrams. It is separating algorithm design from code writing. Most bugs come from doing both at the same time. The diagram is just the tool that forces the separation.

For a two-sum problem, jump straight to code. For a Red-Black tree insertion, draw the flowchart first — it is the difference between a correct implementation and an all-night debugging session.

The notation does not matter: paper, whiteboard, a mermaid diagram in a Markdown file. What matters is the habit of resolving the structure before committing to the code. Every hour spent designing is three hours saved debugging. The return rate only improves as the problems get harder.

Comments (0)

No comments yet. Be the first to comment!

Leave a comment