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.
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.
"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": "..."}
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)
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
Example 1: Binary Search
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 Construct | Python Translation |
|---|---|
SET x TO value | x = value |
IF condition / ENDIF | if condition: |
ELIF condition | elif condition: |
ELSE | else: |
WHILE condition / ENDWHILE | while condition: |
FOR each item IN collection | for item in collection: |
FUNCTION name(params) | def name(params): |
RETURN value | return value |
BREAK | break |
CONTINUE | continue |
APPEND item TO list | list.append(item) |
REMOVE item FROM list | list.remove(item) |
length(collection) | len(collection) |
TRY / CATCH error | try: / except ErrorType: |
RAISE error | raise 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)
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:
- Gradients must be cleared at the start of each batch - not each epoch (a common silent bug)
- Model must switch between training mode and evaluation mode (affects Dropout, BatchNorm)
- Gradient tracking must be disabled during validation (saves memory, speeds evaluation)
- The
patiencecounter must reset when improvement occurs - not just decrement - 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
| Concept | Pseudocode Notation | Python Equivalent |
|---|---|---|
| Assignment | SET x TO value | x = value |
| Conditional | IF cond / ENDIF | if cond: |
| Loop | WHILE cond / ENDWHILE | while cond: |
| Iteration | FOR each x IN collection | for x in collection: |
| Function | FUNCTION name(params) | def name(params): |
| Return | RETURN value | return value |
| Error | RAISE error message | raise ValueError(msg) |
| Guard | IF invalid: RETURN error | if invalid: return error |
| State init | SET total TO 0 | total = 0 |
| Append | APPEND x TO list | list.append(x) |
| Try/Catch | TRY ... CATCH error | try: ... except Error: |
| Break | BREAK | break |
| Continue | CONTINUE | continue |
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:
- Tasks have a name, a priority level (1=low, 5=high), and optional dependencies (a list of task names that must complete first)
- The scheduler should always run the highest-priority task whose dependencies are all completed
- If no runnable tasks remain, report the blocked tasks and their unmet dependencies
- Each task has a timeout in seconds; if it exceeds the timeout, mark it as failed and log the failure
- 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:
- Failed tasks should NOT be in
completed_tasks- they must not unblock dependents - The
runnablelist must be rebuilt each iteration as completions change dependency status - Sorting
runnableby priority descending ensures highest-priority task runs next - Blocked detection (empty
runnablewith non-emptypending) prevents an infinite loop - 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
