Skip to main content

Pseudocode - Design Before You Code

A senior engineer once told me: "I can predict whether a codebase will be a mess in five years - just show me their pseudocode."

Not their code. Their pseudocode.

Because messy thinking becomes messy code, and messy code becomes unmaintainable systems.

The engineers who write the cleanest production systems are not the fastest typists. They are the most deliberate thinkers. They design logic before they open their editor. They argue with themselves on paper before they argue with a compiler.

Pseudocode is the discipline of thinking before typing. It is not a formality. It is an engineering practice that separates developers from engineers.

What You Will Learn

  • What pseudocode is and what it is not
  • How to write pseudocode at the right level of abstraction
  • The core control structures expressed in pseudocode notation
  • The defensive design principle: handle failures first
  • How to track state explicitly in algorithm design
  • Real engineering examples: binary search, rate limiter, retry logic
  • How to mechanically translate pseudocode into Python
  • When pseudocode is worth the overhead and when it is not
  • How to apply pseudocode thinking to machine learning systems

Prerequisites

  • You have written basic Python (variables, loops, conditionals)
  • You have encountered at least one logic bug in your own code
  • You understand that programs have inputs, outputs, and failure cases

Mental Model: The Engineering Workflow

PROBLEM STATEMENT


PSEUDOCODE ← Design stage (this lesson)
(language-agnostic)


PYTHON CODE ← Implementation stage
(syntax-specific)


TESTS ← Verification stage

Most beginners skip the middle layer. They jump from problem directly to code. The result: logic bugs buried in syntax, algorithms that work for happy paths but fail on edge cases, and code that is impossible to reason about.

Pseudocode forces the design layer to exist.

Part 1 - What Pseudocode Is (and Is Not)

Pseudocode is a structured, language-agnostic description of an algorithm.

It captures:

  • What the input is
  • What the output is
  • What decisions are made, and on what conditions
  • What the loop conditions are
  • What the failure cases are
  • How state changes through the algorithm

It is NOT:

  • Vague comments ("do stuff", "process the data", "handle errors")
  • Actual Python code written without indentation
  • A flowchart (which is visual; pseudocode is textual)
  • A formal specification language with strict grammar rules

The goal is precision without the distraction of syntax.

tip

If you cannot write clear pseudocode for an algorithm, you do not yet understand the algorithm well enough to implement it.

The Test: If another engineer read your pseudocode and could implement it in any language - Java, Go, C, Python - it is good pseudocode. If they would ask clarifying questions, it needs more detail.

Part 2 - Levels of Abstraction

Pseudocode exists on a spectrum. The right level depends on complexity.

Consider the problem: sort a list of numbers.

Too vague - says nothing:

Sort the list
Return result

This communicates no algorithm. Any reader could interpret it ten different ways.

Too specific - copies Python syntax:

pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quicksort(left) + middle + quicksort(right)

This is not pseudocode. It is Python with different indentation. It gives no benefit over just writing the code.

Just right - describes the algorithm clearly:

INPUT: list of numbers
OUTPUT: same list, sorted ascending

FOR each unsorted portion of the list:
FIND the minimum element in the unsorted portion
SWAP it to the front of the unsorted portion

RETURN the sorted list

This is selection sort in pseudocode. Any engineer can implement it. It communicates the algorithm, not the syntax.

The Rule: Start high-level. Add detail only where ambiguity remains. Stop when a competent engineer could implement each step without further questions.

note

"Unsorted portion" in the pseudocode above is still abstract enough - an engineer implementing it would make their own choices about index management. That is appropriate. Pseudocode describes strategy, not every variable name.

Part 3 - Core Control Structures in Pseudocode

Every algorithm is built from a small set of control structures. Here is a consistent notation:

SEQUENCE - Steps in order

SET variable TO value
COMPUTE result FROM expression
CALL function WITH arguments

IF / ELIF / ELSE - Decisions

IF condition
action
ELIF other_condition
other_action
ELSE
default_action
ENDIF

WHILE - Condition-based loop

WHILE condition
action
ENDWHILE

FOR - Iteration over a collection

FOR each item IN collection
action
ENDFOR

FUNCTION definitions

FUNCTION name(parameters)
body
RETURN value
ENDFUNCTION

BREAK and CONTINUE

FOR each item IN collection
IF item meets stop condition
BREAK
IF item should be skipped
CONTINUE
process item
ENDFOR

Complete example - find the maximum value in a list:

Weak pseudocode (too vague):

Loop list
Find max
Return it

Strong pseudocode:

FUNCTION find_max(numbers)
INPUT: a non-empty list of numbers
OUTPUT: the largest number in the list

IF numbers is empty
RETURN error "cannot find max of empty list"
ENDIF

SET max_value TO first element of numbers

FOR each number IN numbers
IF number > max_value
SET max_value TO number
ENDIF
ENDFOR

RETURN max_value
ENDFUNCTION

Python translation (mechanical - follows the pseudocode exactly):

def find_max(numbers):
if not numbers:
raise ValueError("cannot find max of empty list")

max_value = numbers[0]

for number in numbers:
if number > max_value:
max_value = number

return max_value

# Output:
print(find_max([3, 1, 4, 1, 5, 9, 2, 6])) # 9
print(find_max([])) # raises ValueError

Notice: the edge case (numbers is empty) was caught at the pseudocode stage, not discovered during debugging.

Part 4 - The Defensive Design Principle

The most important pattern in professional algorithm design is: handle failures first, success last.

This is called the Guard Clause pattern. Each failure condition exits immediately before the main logic runs.

Scenario: Design a login system.

Bad pseudocode (nested approach):

IF username exists
IF password is correct
IF account is active
GRANT access
ELSE
RETURN "account inactive"
ENDIF
ELSE
RETURN "wrong password"
ENDIF
ELSE
RETURN "user not found"
ENDIF

This creates a pyramid of nested conditions. The deeper you read, the harder it is to reason about. The happy path is buried at the deepest nesting level.

Good pseudocode (guard clause approach):

FUNCTION login(username, password)
INPUT: username string, password string
OUTPUT: success message or error

IF username does not exist IN user_database
RETURN "user not found"
ENDIF

IF password does NOT match stored_hash FOR username
RETURN "wrong password"
ENDIF

IF account status IS NOT "active"
RETURN "account inactive"
ENDIF

GENERATE session token
RETURN "access granted" WITH session_token
ENDFUNCTION

Each failure exits immediately. The happy path is always at the bottom, clear and linear.

Python translation:

def login(username, password, user_database):
if username not in user_database:
return {"error": "user not found"}

user = user_database[username]

if not check_password(password, user["password_hash"]):
return {"error": "wrong password"}

if user["status"] != "active":
return {"error": "account inactive"}

token = generate_session_token(username)
return {"success": True, "token": token}

# Output:
# login("unknown", "pass", {}) → {"error": "user not found"}
# login("alice", "wrong", db) → {"error": "wrong password"}
# login("alice", "correct", db) → {"success": True, "token": "..."}
warning

Deeply nested conditionals are a sign that failures were not handled at the design stage. Nested if blocks grow to 6 and 7 levels of indentation in real codebases - almost always because nobody wrote guard clauses in pseudocode first.

Part 5 - State-Aware Design

Algorithms have state that changes as they execute. Good pseudocode tracks state explicitly - what variables hold which values at each step.

Forgetting to initialize state is one of the most common logic bugs. It is almost always caught at the pseudocode stage, never caught by a linter.

Example: Shopping cart total with discount

Buggy pseudocode (state not initialized):

FOR each item IN cart
ADD item.price TO total ← ERROR: total was never initialized to 0
ENDFOR

IF total > 100
SUBTRACT (total * 0.1) FROM total
ENDIF

RETURN total

This pseudocode has a state bug: total is never set to zero before the loop. In Python, this produces a NameError. In some languages, it produces silent garbage data.

Correct pseudocode:

FUNCTION calculate_total(cart)
INPUT: list of items, each with a .price field
OUTPUT: total price after any applicable discounts

SET total TO 0

FOR each item IN cart
ADD item.price TO total
ENDFOR

IF total > 100
SET discount TO total * 0.1
SUBTRACT discount FROM total
ENDIF

RETURN total
ENDFUNCTION

Python translation:

def calculate_total(cart):
total = 0 # State initialized explicitly

for item in cart:
total += item["price"]

if total > 100:
discount = total * 0.1
total -= discount

return round(total, 2)

# Output:
cart = [{"price": 40}, {"price": 50}, {"price": 30}]
print(calculate_total(cart)) # 108.0 (120 - 12.0 discount)

cart2 = [{"price": 20}, {"price": 30}]
print(calculate_total(cart2)) # 50 (no discount, under 100)
tip

In pseudocode, explicitly write SET total TO 0 rather than assuming the reader knows it starts at zero. Explicit state management is not verbosity - it is precision. The bug you prevent at the pseudocode stage costs zero debugging time.

Part 6 - Real Engineering Examples

FUNCTION binary_search(sorted_list, target)
INPUT: a sorted list of numbers, a target value
OUTPUT: index of target, or -1 if not found

SET low TO 0
SET high TO length(sorted_list) - 1

WHILE low <= high
SET mid TO (low + high) / 2 (integer division)

IF sorted_list[mid] EQUALS target
RETURN mid
ELIF sorted_list[mid] < target
SET low TO mid + 1
ELSE
SET high TO mid - 1
ENDIF
ENDWHILE

RETURN -1 (target not found)
ENDFUNCTION

Python translation:

def binary_search(sorted_list, target):
low = 0
high = len(sorted_list) - 1

while low <= high:
mid = (low + high) // 2

if sorted_list[mid] == target:
return mid
elif sorted_list[mid] < target:
low = mid + 1
else:
high = mid - 1

return -1

# Output:
nums = [1, 3, 5, 7, 9, 11, 13, 15]
print(binary_search(nums, 7)) # 3
print(binary_search(nums, 6)) # -1
print(binary_search(nums, 1)) # 0
print(binary_search(nums, 15)) # 7

Example 2: Rate Limiter (10 requests per minute)

FUNCTION check_rate_limit(user_id, current_timestamp)
INPUT: user identifier, current Unix timestamp in seconds
OUTPUT: ALLOW or BLOCK

RETRIEVE timestamps list FOR user_id FROM store
IF no list exists
CREATE empty list FOR user_id
ENDIF

REMOVE all timestamps older than (current_timestamp - 60 seconds)

IF length of timestamps list < 10
APPEND current_timestamp TO timestamps list
SAVE timestamps list FOR user_id
RETURN ALLOW
ELSE
RETURN BLOCK
ENDIF
ENDFUNCTION

Python translation:

import time

request_store = {} # In production: Redis

def check_rate_limit(user_id, current_timestamp=None):
if current_timestamp is None:
current_timestamp = time.time()

if user_id not in request_store:
request_store[user_id] = []

# Remove timestamps older than 60 seconds
cutoff = current_timestamp - 60
request_store[user_id] = [
ts for ts in request_store[user_id] if ts > cutoff
]

if len(request_store[user_id]) < 10:
request_store[user_id].append(current_timestamp)
return "ALLOW"
else:
return "BLOCK"

# Output:
now = time.time()
for i in range(12):
result = check_rate_limit("user_123", now + i * 0.1)
print(f"Request {i+1}: {result}")
# Requests 1-10: ALLOW
# Requests 11-12: BLOCK

Example 3: Retry Logic with Exponential Backoff

FUNCTION execute_with_retry(operation, max_attempts)
INPUT: an operation function, maximum retry count
OUTPUT: result of the operation, or failure after max_attempts

SET attempt TO 1
SET wait_seconds TO 1

WHILE attempt <= max_attempts
TRY
EXECUTE operation
RETURN result
CATCH network_error
IF attempt EQUALS max_attempts
RAISE "operation failed after all retries"
ENDIF
WAIT wait_seconds
SET wait_seconds TO wait_seconds * 2 (exponential backoff)
INCREMENT attempt BY 1
ENDTRY
ENDWHILE
ENDFUNCTION

Python translation:

import time

def execute_with_retry(operation, max_attempts=3):
attempt = 1
wait_seconds = 1

while attempt <= max_attempts:
try:
result = operation()
return result
except (ConnectionError, TimeoutError) as e:
if attempt == max_attempts:
raise RuntimeError(f"Operation failed after {max_attempts} attempts") from e
print(f"Attempt {attempt} failed. Retrying in {wait_seconds}s...")
time.sleep(wait_seconds)
wait_seconds *= 2
attempt += 1

# Output:
# Attempt 1 failed. Retrying in 1s...
# Attempt 2 failed. Retrying in 2s...
# RuntimeError: Operation failed after 3 attempts

Part 7 - From Pseudocode to Python (Translation Mechanics)

The translation from pseudocode to Python is almost entirely mechanical once pseudocode is well-written.

Pseudocode ConstructPython Translation
SET x TO valuex = value
IF condition / ENDIFif condition:
ELIF conditionelif condition:
ELSEelse:
WHILE condition / ENDWHILEwhile condition:
FOR each item IN collectionfor item in collection:
FUNCTION name(params)def name(params):
RETURN valuereturn value
BREAKbreak
CONTINUEcontinue
APPEND item TO listlist.append(item)
REMOVE item FROM listlist.remove(item)
length(collection)len(collection)
TRY / CATCH errortry: / except ErrorType:
RAISE errorraise ExceptionType(message)

The translation is direct. This is why well-written pseudocode is valuable: implementation becomes a typing exercise, not a thinking exercise. All thinking happened at the pseudocode stage.

Part 8 - When NOT to Use Pseudocode

Pseudocode has overhead. Apply it where complexity justifies the investment.

Skip pseudocode for:

  • Trivial problems (printing "Hello, World!", swapping two variables)
  • Standard algorithms you have implemented dozens of times (a basic for loop, sorting a list with sorted())
  • Problems where the code is shorter than the pseudocode would be
  • Teams fluent in the domain where code comments serve equally well

Use pseudocode for:

  • Novel algorithms you have not implemented before
  • Multi-step logic with several failure conditions
  • Concurrent or distributed system design
  • Algorithms you will need to explain to team members or in interviews
  • Systems where a logic bug has serious consequences (payments, authentication, data deletion)
danger

Writing pseudocode AFTER the code defeats the entire purpose. Pseudocode is a design tool, not documentation. If you find yourself transcribing existing code into pseudocode format, stop - you have reversed the workflow.

AI/ML Real-World Connection

Machine learning engineers who do not pseudocode their training loops build systems with missing pieces they discover only at hour 14 of GPU training time. Pseudocode reveals the gaps before a single forward pass runs.

Here is a complete pseudocode design for a supervised learning training loop:

FUNCTION train_model(dataset, model, num_epochs)

LOAD dataset
SPLIT dataset INTO train_set AND validation_set

INITIALIZE model weights (random or pretrained)
SET optimizer (Adam, learning_rate=0.001)
SET best_validation_loss TO infinity
SET epochs_without_improvement TO 0
SET patience TO 5

FOR each epoch FROM 1 TO num_epochs

SET model TO training mode
SET train_loss TO 0

FOR each batch IN train_set
CLEAR gradients from previous batch
FORWARD pass: compute predictions from batch
COMPUTE loss (predictions vs true labels)
BACKWARD pass: compute gradients
UPDATE model weights using optimizer
ADD batch_loss TO train_loss
ENDFOR

SET model TO evaluation mode

SET validation_loss TO 0
FOR each batch IN validation_set
FORWARD pass (no gradient tracking)
COMPUTE loss
ADD to validation_loss
ENDFOR

COMPUTE average train_loss AND validation_loss

IF validation_loss < best_validation_loss
SET best_validation_loss TO validation_loss
SAVE model checkpoint
SET epochs_without_improvement TO 0
ELSE
INCREMENT epochs_without_improvement
ENDIF

IF epochs_without_improvement >= patience
PRINT "Early stopping at epoch N"
BREAK
ENDIF

ENDFOR

LOAD best model checkpoint
RETURN model

ENDFUNCTION

What this pseudocode reveals before you write a single line of PyTorch:

  1. Gradients must be cleared at the start of each batch - not each epoch (a common silent bug)
  2. Model must switch between training mode and evaluation mode (affects Dropout, BatchNorm)
  3. Gradient tracking must be disabled during validation (saves memory, speeds evaluation)
  4. The patience counter must reset when improvement occurs - not just decrement
  5. The final model returned should be the best checkpoint, not the last epoch

Every one of these is a real training loop bug that novice ML engineers discover after wasting GPU hours. Pseudocode catches them for free.

Common Mistakes

Mistake 1 - Writing pseudocode after the code

# Bug introduced: wrote code first, then pseudocode to document it
def process_orders(orders):
results = []
for order in orders:
if order.status == "pending":
results.append(process(order)) # What does process() do on failure?
return results

The pseudocode would have revealed: what happens when process(order) fails? Should we skip? Log? Abort? Writing first, thinking second, costs debugging time later.

Mistake 2 - Wrong abstraction level

Too vague (tells us nothing):

Get data
Process it
Return result

Too specific (just Python in disguise):

results = [item["value"] * 1.1 for item in data if item["active"] == True]

Appropriate level:

FOR each active item IN data
COMPUTE adjusted_value AS item.value multiplied by 1.1
ADD adjusted_value TO results
ENDFOR

Mistake 3 - Missing edge cases in pseudocode

FUNCTION get_first_item(collection)
RETURN collection[0]
ENDFUNCTION

This pseudocode has a missing guard. What if collection is empty? The pseudocode should read:

FUNCTION get_first_item(collection)
IF collection is empty
RETURN error "collection is empty"
ENDIF
RETURN collection[0]
ENDFUNCTION

The guard clause omission in pseudocode becomes an IndexError or silent None bug in production.

Interview Questions

Q1. What is pseudocode and why do engineers use it?

Answer: Pseudocode is a structured, language-agnostic description of an algorithm that captures logic - inputs, outputs, decisions, loops, and failure cases - without the distraction of syntax. Engineers use it because most bugs in early-stage development are logic errors, not syntax errors. Writing pseudocode before code forces the design to be explicit, catches missing edge cases at zero cost, and makes the eventual Python translation nearly mechanical. It also enables non-Python engineers to review algorithm correctness without needing to read the language.

Q2. How do you handle edge cases in pseudocode?

Answer: By explicitly asking - before writing a single operation - what can go wrong with the input. For every function, the questions are: What if the input is empty? What if the input is of the wrong type? What if a number is negative when it should be positive? What if a required external resource is unavailable? These cases are written as guard clauses at the top of the pseudocode function, each with an explicit RETURN error or RAISE exception. This ensures that by the time the happy path is implemented, all failure modes are already handled.

Q3. What is the difference between pseudocode and code comments?

Answer: Pseudocode is written before the code exists. It is a design document. Code comments are written after (or alongside) code, explaining implementation decisions. Pseudocode helps you think through the algorithm. Comments help others read the code you already wrote. They serve different phases of the engineering process. Writing pseudocode after the code is not pseudocode - it is reverse-engineering documentation, and it defeats the entire purpose of the exercise.

Q4. When should you use pseudocode versus jumping straight to code?

Answer: Pseudocode earns its overhead when the algorithm is novel, when there are multiple failure conditions, when the algorithm will be reviewed by others, or when a logic bug has serious consequences (payments, data deletion, authentication). For trivial operations - printing a value, a basic loop over a list - pseudocode adds overhead without benefit. The threshold: if you can write the code in two minutes without thinking, skip pseudocode. If you have to stop and think about what happens in three different cases, write pseudocode first.

Q5. What is the guard clause pattern and why does it reduce code complexity?

Answer: The guard clause pattern handles failure conditions at the top of a function with early returns, before the main logic runs. Each invalid state (missing user, wrong password, inactive account) is checked in sequence, with an immediate return on failure. This eliminates nested conditionals by keeping the happy path at a single indentation level. The cognitive benefit is significant: a reader scanning the function sees all failure modes at the top, and the expected behavior at the bottom. Without guard clauses, failure conditions are buried inside nested if blocks at multiple indentation levels, making the control flow much harder to reason about.

Quick Reference Cheatsheet

ConceptPseudocode NotationPython Equivalent
AssignmentSET x TO valuex = value
ConditionalIF cond / ENDIFif cond:
LoopWHILE cond / ENDWHILEwhile cond:
IterationFOR each x IN collectionfor x in collection:
FunctionFUNCTION name(params)def name(params):
ReturnRETURN valuereturn value
ErrorRAISE error messageraise ValueError(msg)
GuardIF invalid: RETURN errorif invalid: return error
State initSET total TO 0total = 0
AppendAPPEND x TO listlist.append(x)
Try/CatchTRY ... CATCH errortry: ... except Error:
BreakBREAKbreak
ContinueCONTINUEcontinue

Graded Practice Challenges

Level 1 - Write Pseudocode from English

Problem: Given this English description, write clean pseudocode:

"Find all duplicate numbers in a list. A duplicate is any number that appears more than once. Return a list containing each duplicate number only once."

Example: [1, 2, 3, 2, 4, 3, 5][2, 3]

Show Answer
FUNCTION find_duplicates(numbers)
INPUT: list of numbers
OUTPUT: list of numbers that appear more than once (each listed once)

SET seen TO empty set
SET duplicates TO empty set

FOR each number IN numbers
IF number IS IN seen
ADD number TO duplicates
ELSE
ADD number TO seen
ENDIF
ENDFOR

RETURN duplicates as a list
ENDFUNCTION

Python translation:

def find_duplicates(numbers):
seen = set()
duplicates = set()

for number in numbers:
if number in seen:
duplicates.add(number)
else:
seen.add(number)

return list(duplicates)

# Output:
print(find_duplicates([1, 2, 3, 2, 4, 3, 5])) # [2, 3] (order may vary)
print(find_duplicates([1, 1, 1])) # [1]
print(find_duplicates([1, 2, 3])) # []

Key insight: using two sets (seen and duplicates) is the correct state model. Using a single counter dict is also valid - the pseudocode would then be: SET counts TO empty dictionary, FOR each number: INCREMENT counts[number], FOR each key IN counts: IF counts[key] > 1: ADD to duplicates.

Level 2 - Debug the Pseudocode

Problem: This pseudocode has a logic bug. Find it and fix it.

FUNCTION calculate_cart_total(cart)
SET total TO first_item.price ← initialized to first item
FOR each item IN cart
ADD item.price TO total
ENDFOR
RETURN total
ENDFUNCTION

Example run with cart = [{price: 10}, {price: 20}, {price: 30}]: the function returns 70 instead of the correct 60.

Show Answer

The Bug: total is initialized to first_item.price (the price of the first item). Then the FOR loop iterates over ALL items in the cart including the first item, which means the first item's price is counted twice: once during initialization and once during the loop iteration.

The Fix:

FUNCTION calculate_cart_total(cart)
SET total TO 0 ← initialize to zero, not to first item
FOR each item IN cart
ADD item.price TO total
ENDFOR
RETURN total
ENDFUNCTION

Python demonstration:

# Buggy version
def calculate_total_buggy(cart):
total = cart[0]["price"] # Bug: first item counted twice
for item in cart:
total += item["price"]
return total

# Fixed version
def calculate_total_fixed(cart):
total = 0 # Correct: initialize to zero
for item in cart:
total += item["price"]
return total

cart = [{"price": 10}, {"price": 20}, {"price": 30}]
print(calculate_total_buggy(cart)) # 70 (wrong - 10 counted twice)
print(calculate_total_fixed(cart)) # 60 (correct)

This is a state initialization bug. It is caught immediately when you write pseudocode because you are forced to explicitly write the initial value. In code, total = cart[0]["price"] looks plausible and linters will not flag it.

Level 3 - Design Challenge

Problem: Design pseudocode for a priority task scheduler with the following requirements:

  1. Tasks have a name, a priority level (1=low, 5=high), and optional dependencies (a list of task names that must complete first)
  2. The scheduler should always run the highest-priority task whose dependencies are all completed
  3. If no runnable tasks remain, report the blocked tasks and their unmet dependencies
  4. Each task has a timeout in seconds; if it exceeds the timeout, mark it as failed and log the failure
  5. The system should run until all tasks are complete or all remaining tasks are blocked
Show Answer
FUNCTION run_task_scheduler(task_list)
INPUT: list of tasks, each with {name, priority, dependencies, timeout_seconds}
OUTPUT: final status report of all tasks

SET completed_tasks TO empty set
SET failed_tasks TO empty set
SET pending_tasks TO task_list (copy)
SET log TO empty list

WHILE pending_tasks is NOT empty

SET runnable TO empty list

FOR each task IN pending_tasks
SET all_deps_met TO true
FOR each dep IN task.dependencies
IF dep NOT IN completed_tasks
SET all_deps_met TO false
BREAK
ENDIF
ENDFOR
IF all_deps_met IS true
APPEND task TO runnable
ENDIF
ENDFOR

IF runnable is empty
APPEND to log: "BLOCKED tasks: " + names of pending_tasks
APPEND to log: "Unmet dependencies for each blocked task"
BREAK
ENDIF

SORT runnable BY priority DESCENDING
SET next_task TO first item in runnable

REMOVE next_task FROM pending_tasks

TRY
EXECUTE next_task.action WITH timeout next_task.timeout_seconds
ADD next_task.name TO completed_tasks
APPEND to log: "COMPLETED: " + next_task.name
CATCH timeout_error
ADD next_task.name TO failed_tasks
APPEND to log: "FAILED (timeout): " + next_task.name
CATCH any_other_error
ADD next_task.name TO failed_tasks
APPEND to log: "FAILED (error): " + next_task.name
ENDTRY

ENDWHILE

RETURN {completed: completed_tasks, failed: failed_tasks, log: log}
ENDFUNCTION

Key design decisions this pseudocode forces you to think through:

  1. Failed tasks should NOT be in completed_tasks - they must not unblock dependents
  2. The runnable list must be rebuilt each iteration as completions change dependency status
  3. Sorting runnable by priority descending ensures highest-priority task runs next
  4. Blocked detection (empty runnable with non-empty pending) prevents an infinite loop
  5. Timeout and error failures are separate catch clauses with separate logging

This demonstrates the value of pseudocode for a complex scheduling algorithm: the design reveals 5 correctness decisions before a single line of Python is written.

Key Takeaways

  • Pseudocode is a design discipline, not a documentation exercise - it must be written before code, not after
  • The right abstraction level: precise enough that any engineer could implement it, high-level enough that it does not duplicate Python syntax
  • Core constructs: SEQUENCE, IF/ELIF/ELSE, WHILE, FOR, FUNCTION, RETURN, BREAK, CONTINUE - all map mechanically to Python
  • The defensive design principle: handle failures first with guard clauses; the happy path lives at the bottom of the function
  • State-aware design: always explicitly initialize state variables in pseudocode - the most silent class of bugs comes from uninitialized state
  • The translation from good pseudocode to Python is mechanical; all thinking happens at the pseudocode stage
  • Pseudocode pays dividends proportionally to algorithm complexity; skip it only for trivial operations
  • In machine learning, pseudocode reveals missing operations (gradient clearing, mode switching) that cost GPU hours to debug
  • The discipline of writing pseudocode is the discipline of understanding a problem before attempting to solve it
© 2026 EngineersOfAI. All rights reserved.