Python Flowcharts Practice Problems & Exercises
Practice: Flowcharts
← Back to lessonEasy
Implement the following flowchart as a Python function that returns "positive", "negative", or "zero".
┌───────────┐
│ START │
└─────┬─────┘
│
▼
┌─────────────┐
│ Input: n │
└─────┬───────┘
│
▼
╱‾‾‾‾‾‾‾‾╲
╱ n > 0 ? ╲───── Yes ──▶ ┌──────────────┐
╲ ╱ │ "positive" │
╲_________╱ └──────┬───────┘
│ No │
▼ │
╱‾‾‾‾‾‾‾‾╲ │
╱ n < 0 ? ╲───── Yes ──▶ ┌──────────────┐
╲ ╱ │ "negative" │
╲_________╱ └──────┬───────┘
│ No │
▼ │
┌──────────────┐ │
│ "zero" │ │
└──────┬───────┘ │
│ │
▼ ▼
┌─────────────────────────────────┐
│ Return result │
└────────────┬────────────────────┘
│
▼
┌───────────┐
│ END │
└───────────┘
Solution
def classify_sign(n):
if n > 0:
return "positive"
elif n < 0:
return "negative"
else:
return "zero"
# Test
print(classify_sign(42))
print(classify_sign(-7))
print(classify_sign(0))Why it works: Each diamond in the flowchart maps directly to an if/elif condition. The final rectangle ("zero") is the else — the path taken when neither condition is true. This is the most fundamental flowchart-to-code translation: a chain of decisions becomes an if/elif/else ladder.
def classify_sign(n):
# Implement the flowchart logic below
pass
# Test
print(classify_sign(42))
print(classify_sign(-7))
print(classify_sign(0))Expected Output
positive\nnegative\nzeroHints
Hint 1: Follow the diamond shapes top to bottom — each diamond is an if/elif condition.
Hint 2: There are only two decisions needed: check if n > 0 first, then if n < 0. Everything else falls to the else branch.
Implement the following sequential flowchart with a validation branch. The function takes a temperature value and a unit ("C" or "F"), converts it to the other unit, and returns the result as a formatted string. If the unit is invalid, return "Invalid unit".
┌───────────┐
│ START │
└─────┬─────┘
│
▼
┌──────────────────┐
│ Input: value, unit│
└────────┬─────────┘
│
▼
╱‾‾‾‾‾‾‾‾‾‾╲
╱ unit == "C"? ╲─── Yes ──▶ ┌─────────────────────────┐
╲ ╱ │ result = value*9/5 + 32 │
╲___________╱ └──────────┬──────────────┘
│ No │
▼ ▼
╱‾‾‾‾‾‾‾‾‾‾╲ ┌──────────────────┐
╱ unit == "F"? ╲─── Yes ──▶ │ result = │
╲ ╱ │ (value-32)*5/9 │
╲___________╱ └────────┬─────────┘
│ No │
▼ ▼
┌────────────────┐ ┌──────────────────────┐
│ "Invalid unit" │ │ Format: "{result}°X" │
└───────┬────────┘ └──────────┬───────────┘
│ │
▼ ▼
┌──────────────────────────────────────┐
│ Return │
└──────────────────┬────────────────────┘
▼
┌───────────┐
│ END │
└───────────┘
Solution
def convert_temperature(value, unit):
if unit == "C":
result = (value * 9 / 5) + 32
return f"{result}°F"
elif unit == "F":
result = (value - 32) * 5 / 9
return f"{result}°C"
else:
return "Invalid unit"
# Test
print(convert_temperature(100, "C"))
print(convert_temperature(32, "F"))
print(convert_temperature(100, "K"))Why it works: This flowchart has a sequential structure with a validation gate. The two "Yes" paths each perform a calculation and format the result. The "No" fallthrough catches any invalid unit. Notice how the flowchart's two separate formatting boxes (°F and °C) become two separate return statements in code — the flowchart makes it visually obvious that different units produce different output labels.
def convert_temperature(value, unit):
# Implement the flowchart logic below
pass
# Test
print(convert_temperature(100, "C"))
print(convert_temperature(32, "F"))
print(convert_temperature(100, "K"))Expected Output
212.0°F\n0.0°C\nInvalid unitHints
Hint 1: The first diamond checks the unit — this becomes an if/elif/else structure.
Hint 2: Celsius to Fahrenheit: (value * 9/5) + 32. Fahrenheit to Celsius: (value - 32) * 5/9.
Implement this loop flowchart. The function takes a list of numbers and a sentinel value (default -1). It sums numbers from left to right, stopping as soon as it encounters the sentinel. If the sentinel is never found, sum all numbers.
┌───────────┐
│ START │
└─────┬─────┘
│
▼
┌──────────────────┐
│ total = 0 │
│ i = 0 │
└────────┬─────────┘
│
▼
╱‾‾‾‾‾‾‾‾‾‾‾‾‾╲
╱ i < len(nums)? ╲──── No ──▶ ┌──────────────┐
╲ ╱ │ Return total │
╲_______________╱ └──────────────┘
│ Yes
▼
╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲
╱ nums[i] == sentinel?╲── Yes ──▶ ┌──────────────┐
╲ ╱ │ Return total │
╲__________________╱ └──────────────┘
│ No
▼
┌────────────────────┐
│ total += nums[i] │
│ i += 1 │
└────────┬───────────┘
│
└───────┐
│ (loop back)
┌───────┘
│
▼
(back to "i < len(nums)?")
Solution
def sum_until_sentinel(numbers, sentinel=-1):
total = 0
for num in numbers:
if num == sentinel:
break
total += num
return total
# Test
print(sum_until_sentinel([5, 10, 3, -1, 99]))
print(sum_until_sentinel([1, 2, 3, 4, 5]))
print(sum_until_sentinel([-1, 100, 200]))Why it works: The flowchart shows a loop with two exit conditions: running out of elements (the outer diamond) and hitting the sentinel (the inner diamond). In Python, a for loop handles the first condition automatically, and break handles the second. The flowchart's backward arrow becomes the implicit "next iteration" of the for loop. This is the most common loop flowchart pattern — check a condition, process, repeat.
def sum_until_sentinel(numbers, sentinel=-1):
# Implement the flowchart logic below
pass
# Test
print(sum_until_sentinel([5, 10, 3, -1, 99]))
print(sum_until_sentinel([1, 2, 3, 4, 5]))
print(sum_until_sentinel([-1, 100, 200]))Expected Output
18\n15\n0Hints
Hint 1: The diamond inside the loop checks each number before adding — this is a classic "process or stop" pattern.
Hint 2: Use a for loop with a break statement when you hit the sentinel, or iterate and check each element.
Medium
Implement this login system flowchart. The system allows up to max_attempts password tries. On each incorrect attempt, it reports the remaining tries. On the final failed attempt, it locks the account. On a correct attempt, it reports success.
┌───────────────┐
│ START │
└───────┬───────┘
│
▼
┌──────────────────┐
│ failures = 0 │
│ attempt_num = 0 │
└────────┬─────────┘
│
▼
┌────▶ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲
│ ╱ attempt_num < max AND ╲
│ ╱ attempt_num < len(list)? ╲──── No ──▶ ┌────────────────┐
│ ╲ ╱ │ Return messages│
│ ╲________________________╱ └────────────────┘
│ │ Yes
│ ▼
│ ┌──────────────────────┐
│ │ attempt_num += 1 │
│ │ pw = list[attempt_num]│
│ └──────────┬───────────┘
│ │
│ ▼
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾╲
│ ╱ pw == correct? ╲──── Yes ──▶ ┌──────────────────┐
│ ╲ ╱ │ Append "Login │
│ ╲______________╱ │ successful!" │
│ │ No └────────┬─────────┘
│ ▼ │
│ ┌──────────────────┐ ▼
│ │ failures += 1 │ ┌────────────────┐
│ └────────┬─────────┘ │ Return messages│
│ │ └────────────────┘
│ ▼
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲
│ ╱ failures >= max? ╲── Yes ──▶ ┌────────────────────┐
│ ╲ ╱ │ Append "Account │
│ ╲___________________╱ │ locked." │
│ │ No └──────┬─────────────┘
│ ▼ │
│ ┌──────────────────────┐ ▼
│ │ Append "Incorrect, │ ┌────────────────┐
│ │ N tries remaining." │ │ Return messages│
│ └──────────┬──────────┘ └────────────────┘
│ │
└────────────────┘
Solution
def login_system(attempts_list, correct_password, max_attempts=3):
messages = []
failures = 0
for i, pw in enumerate(attempts_list):
attempt_num = i + 1
if pw == correct_password:
messages.append(f"Attempt {attempt_num}: Login successful!")
return messages
failures += 1
if failures >= max_attempts:
messages.append(f"Attempt {attempt_num}: Incorrect password. Account locked.")
return messages
remaining = max_attempts - failures
messages.append(f"Attempt {attempt_num}: Incorrect password. {remaining} tries remaining.")
return messages
# Test
print(login_system(["wrong", "wrong", "secret"], "secret"))
print(login_system(["wrong", "wrong", "wrong"], "secret"))
print(login_system(["secret"], "secret"))Why it works: The flowchart has three exit points — success, lockout, and running out of attempts. Each maps to a return statement. The inner decision diamond (password check) becomes an if that short-circuits on success. The lockout check (failures >= max) is a second if after incrementing. The backward arrow in the flowchart is the implicit next iteration of the for loop. Multiple exit points in a flowchart naturally translate to early returns in Python.
def login_system(attempts_list, correct_password, max_attempts=3):
"""Simulate a login system.
attempts_list: list of password strings the user tries
correct_password: the valid password
max_attempts: lockout after this many failures
Returns a list of messages for each step.
"""
messages = []
# Implement the flowchart logic below
return messages
# Test
print(login_system(["wrong", "wrong", "secret"], "secret"))
print(login_system(["wrong", "wrong", "wrong"], "secret"))
print(login_system(["secret"], "secret"))Expected Output
['Attempt 1: Incorrect password. 2 tries remaining.', 'Attempt 2: Incorrect password. 1 tries remaining.', 'Attempt 3: Login successful!']\n['Attempt 1: Incorrect password. 2 tries remaining.', 'Attempt 2: Incorrect password. 1 tries remaining.', 'Attempt 3: Incorrect password. Account locked.']\n['Attempt 1: Login successful!']Hints
Hint 1: Track a failure_count variable. Each wrong attempt increments it. The loop ends on success OR when failure_count reaches max_attempts.
Hint 2: On each attempt: check if it matches the correct password. If yes, append success and break. If no, increment failure_count and check if locked.
Implement this multi-branch decision flowchart for a grade calculator. If the student has extra credit, add 5 points (capped at 100) before determining the grade. Each grade has an associated status message.
┌──────────────┐
│ START │
└──────┬───────┘
│
▼
┌─────────────────────┐
│ Input: score, │
│ has_extra │
└─────────┬───────────┘
│
▼
╱‾‾‾‾‾‾‾‾‾‾‾‾╲
╱ has_extra_credit?╲── Yes ──▶ ┌─────────────────────┐
╲ ╱ │ score = min(score │
╲_______________╱ │ + 5, 100) │
│ No └──────────┬──────────┘
│◀──────────────────────────────────┘
▼
╱‾‾‾‾‾‾‾‾‾╲
╱ score >= 97?╲── Yes ──▶ Return ("A+", "Outstanding")
╲ ╱
╲__________╱
│ No
▼
╱‾‾‾‾‾‾‾‾‾╲
╱ score >= 90?╲── Yes ──▶ Return ("A", "Excellent")
╲ ╱
╲__________╱
│ No
▼
╱‾‾‾‾‾‾‾‾‾╲
╱ score >= 80?╲── Yes ──▶ Return ("B", "Good")
╲ ╱
╲__________╱
│ No
▼
╱‾‾‾‾‾‾‾‾‾╲
╱ score >= 70?╲── Yes ──▶ Return ("C", "Average")
╲ ╱
╲__________╱
│ No
▼
╱‾‾‾‾‾‾‾‾‾╲
╱ score >= 60?╲── Yes ──▶ Return ("D", "Below average")
╲ ╱
╲__________╱
│ No
▼
Return ("F", "Failing")
Solution
def calculate_grade(score, has_extra_credit=False):
if has_extra_credit:
score = min(score + 5, 100)
if score >= 97:
return ("A+", "Outstanding")
elif score >= 90:
return ("A", "Excellent")
elif score >= 80:
return ("B", "Good")
elif score >= 70:
return ("C", "Average")
elif score >= 60:
return ("D", "Below average")
else:
return ("F", "Failing")
# Test
print(calculate_grade(92))
print(calculate_grade(85, True))
print(calculate_grade(58))
print(calculate_grade(45))
print(calculate_grade(97, True))Why it works: The flowchart has two distinct sections. First, a preprocessing step (extra credit) guarded by a single diamond. Then a cascading decision chain where each "No" path falls through to the next check. This maps perfectly to an if/elif/elif/.../else chain. The order of checks matters — because we check >= 97 before >= 90, we guarantee the correct bucket. If you reversed the order, every score above 90 would match >= 90 first and never reach >= 97.
def calculate_grade(score, has_extra_credit=False):
"""Return (letter_grade, status) tuple.
Apply extra credit (+5 to score, capped at 100) before grading.
"""
# Implement the flowchart logic below
pass
# Test
print(calculate_grade(92))
print(calculate_grade(85, True))
print(calculate_grade(58))
print(calculate_grade(45))
print(calculate_grade(97, True))Expected Output
('A', 'Excellent')\n('A', 'Excellent')\n('D', 'Below average')\n('F', 'Failing')\n('A+', 'Outstanding')Hints
Hint 1: Handle extra credit first (before the grade checks) — the flowchart processes it as a separate step at the top.
Hint 2: After adjusting the score, the grade boundaries are: 97+ = A+, 90+ = A, 80+ = B, 70+ = C, 60+ = D, below 60 = F.
Implement this file processing pipeline flowchart. The system validates the filename, checks the content, verifies the extension, then processes the file accordingly.
┌───────────┐
│ START │
└─────┬─────┘
│
▼
┌──────────────────────┐
│ Input: filename, │
│ content │
└────────┬─────────────┘
│
▼
╱‾‾‾‾‾‾‾‾‾‾‾‾‾╲
╱ filename empty? ╲── Yes ──▶ Return error: "Filename is empty"
╲ ╱
╲_______________╱
│ No
▼
╱‾‾‾‾‾‾‾‾‾‾‾‾╲
╱ content empty? ╲── Yes ──▶ Return error: "File content is empty"
╲ ╱
╲______________╱
│ No
▼
┌──────────────────────┐
│ ext = get extension │
└────────┬─────────────┘
│
▼
╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲
╱ ext in (.csv,.txt)?╲── No ──▶ Return error: "Unsupported file type"
╲ ╱
╲_________________╱
│ Yes
▼
╱‾‾‾‾‾‾‾‾‾╲
╱ ext == .csv?╲── Yes ──▶ ┌─────────────────────────┐
╲ ╱ │ Split lines, skip │
╲__________╱ │ header, split by comma │
│ No └──────────┬──────────────┘
▼ │
┌──────────────────┐ │
│ Split into lines │ │
└────────┬─────────┘ │
│ │
▼ ▼
┌─────────────────────────────────────────────┐
│ Return success with record count and data │
└─────────────────────┬───────────────────────┘
▼
┌───────────┐
│ END │
└───────────┘
Solution
def process_file(filename, content):
# Validation: filename
if not filename:
return {"status": "error", "message": "Filename is empty"}
# Validation: content
if not content:
return {"status": "error", "message": "File content is empty"}
# Extract extension
dot_index = filename.rfind(".")
ext = filename[dot_index:] if dot_index != -1 else ""
# Validation: supported type
if ext not in (".csv", ".txt"):
return {"status": "error", "message": f"Unsupported file type: {ext}"}
# Process based on type
if ext == ".csv":
lines = content.split("\n")
data = [line.split(",") for line in lines[1:] if line.strip()]
else:
data = [line for line in content.split("\n") if line.strip()]
return {
"status": "success",
"message": f"Processed {len(data)} records",
"data": data,
}
# Test
print(process_file("data.csv", "name,age\nAlice,30\nBob,25"))
print(process_file("", "some data"))
print(process_file("notes.txt", ""))
print(process_file("image.exe", "binary data"))Why it works: This flowchart demonstrates the "validation pipeline" pattern — a series of guard checks at the top, each with an early-exit error path. In code, each diamond that exits on "Yes" (error case) becomes an if ... return block. Only data that passes every check reaches the processing stage. This pattern is extremely common in production code: validate inputs before doing any real work.
def process_file(filename, content):
"""Simulate a file processing pipeline.
Returns a dict with 'status', 'message', and optionally 'data'.
"""
# Implement the flowchart logic below
pass
# Test
print(process_file("data.csv", "name,age\nAlice,30\nBob,25"))
print(process_file("", "some data"))
print(process_file("notes.txt", ""))
print(process_file("image.exe", "binary data"))Expected Output
{'status': 'success', 'message': 'Processed 2 records', 'data': [['Alice', '30'], ['Bob', '25']]}\n{'status': 'error', 'message': 'Filename is empty'}\n{'status': 'error', 'message': 'File content is empty'}\n{'status': 'error', 'message': 'Unsupported file type: .exe'}Hints
Hint 1: Follow the flowchart top-down — each validation diamond either rejects (returns error) or passes through to the next check.
Hint 2: Supported extensions are .csv and .txt. For CSV, split by lines then by commas (skip header). For TXT, return lines as data.
Implement this vending machine state flowchart. The machine accepts coins, lets the user select products, and handles cancellation with refunds.
┌───────────┐
│ START │
└─────┬─────┘
│
▼
┌──────────────────┐
│ balance = 0.00 │
│ prices = {..} │
└────────┬─────────┘
│
┌──────── ▼ ◀────────────────────────────────────┐
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ more actions? ╲── No ──▶ ┌───────────┐ │
│ ╲ ╱ │ END │ │
│ ╲________________╱ └───────────┘ │
│ │ Yes │
│ ▼ │
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ action type? ╲ │
│ ╲ ╱ │
│ ╲______________╱ │
│ │ │ │ │
│ insert select cancel │
│ │ │ │ │
│ ▼ │ ▼ │
│ ┌────────┐ │ ┌──────────────┐ │
│ │balance │ │ │msg: "Refund: │ │
│ │+= amt │ │ │ $balance" │ │
│ │msg: │ │ │balance = 0 │ │
│ │"Insert │ │ └──────┬───────┘ │
│ │ ed $X" │ │ │ │
│ └───┬────┘ │ │ │
│ │ ▼ │ │
│ │ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ │ ╱balance >= price? ╲ │
│ │ ╲ ╱ │
│ │ ╲_______________╱ │
│ │ │ Yes │ No │
│ │ ▼ ▼ │
│ │ ┌────────┐ ┌────────────────┐ │
│ │ │Dispense│ │"Need $X more." │ │
│ │ │change= │ └───────┬────────┘ │
│ │ │bal-prc │ │ │
│ │ │bal = 0 │ │ │
│ │ └───┬────┘ │ │
│ │ │ │ │
│ └─────┴──────────────┴─────────────────────┘
Solution
def vending_machine(actions):
messages = []
balance = 0.00
prices = {"cola": 1.50, "chips": 1.00, "water": 0.75}
for action in actions:
action_type = action[0]
if action_type == "insert":
amount = action[1]
balance += amount
messages.append(f"Inserted ${amount:.2f}. Balance: ${balance:.2f}")
elif action_type == "select":
product = action[1]
price = prices.get(product, None)
if price is None:
messages.append(f"Unknown product: {product}")
elif balance >= price:
change = balance - price
balance = 0.00
messages.append(f"Dispensing {product}. Change: ${change:.2f}")
else:
shortfall = price - balance
messages.append(f"Insufficient funds for {product}. Need ${shortfall:.2f} more.")
elif action_type == "cancel":
messages.append(f"Transaction cancelled. Refund: ${balance:.2f}")
balance = 0.00
return messages
# Test
print(vending_machine([
("insert", 1.00),
("insert", 0.50),
("select", "cola"),
]))
print(vending_machine([
("insert", 0.50),
("select", "chips"),
("insert", 0.50),
("select", "chips"),
]))
print(vending_machine([
("insert", 1.00),
("cancel",),
]))Why it works: The vending machine flowchart is a state machine — balance is the state, and each action transitions the state. The three-way branch at the "action type?" diamond becomes an if/elif/elif chain. The "select" branch contains a nested decision (enough funds?), which becomes a nested if/else. The backward arrow in the flowchart — looping back to check for more actions — is the for loop. State machines like this are the foundation of most interactive systems.
def vending_machine(actions):
"""Simulate a vending machine.
actions: list of tuples like ('insert', 50), ('select', 'cola'), ('cancel',)
Products: cola=$1.50, chips=$1.00, water=$0.75
Returns list of output messages.
"""
messages = []
# Implement the flowchart logic below
return messages
# Test
print(vending_machine([
("insert", 1.00),
("insert", 0.50),
("select", "cola"),
]))
print(vending_machine([
("insert", 0.50),
("select", "chips"),
("insert", 0.50),
("select", "chips"),
]))
print(vending_machine([
("insert", 1.00),
("cancel",),
]))Expected Output
['Inserted $1.00. Balance: $1.00', 'Inserted $0.50. Balance: $1.50', 'Dispensing cola. Change: $0.00']\n['Inserted $0.50. Balance: $0.50', 'Insufficient funds for chips. Need $0.50 more.', 'Inserted $0.50. Balance: $1.00', 'Dispensing chips. Change: $0.00']\n['Inserted $1.00. Balance: $1.00', 'Transaction cancelled. Refund: $1.00']Hints
Hint 1: Maintain a balance variable that persists across actions. Each action type (insert, select, cancel) is a separate branch in the flowchart.
Hint 2: For "select": check if balance >= price. If yes, dispense and subtract. If no, report the shortfall. For "cancel": refund the full balance and reset to 0.
Hard
Implement this complete ATM transaction flowchart. The system authenticates with a PIN, then processes a series of transactions (check balance, withdraw, deposit), each with full validation.
┌───────────┐
│ START │
└─────┬─────┘
│
▼
┌──────────────────────┐
│ Input: pin, │
│ transactions, │
│ correct_pin, │
│ initial_balance │
└────────┬─────────────┘
│
▼
╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲
╱ pin == correct_pin?╲── No ──▶ ┌─────────────────────────────┐
╲ ╱ │ "Invalid PIN. Session │
╲_________________╱ │ terminated." │
│ Yes └──────────┬──────────────────┘
▼ ▼
┌──────────────────┐ ┌───────────┐
│ "PIN accepted." │ │ END │
│ bal = initial_bal│ └───────────┘
└────────┬─────────┘
│
┌──────── ▼ ◀─────────────────────────────────────┐
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ more transactions?╲── No ──▶ ┌───────────┐ │
│ ╲ ╱ │ END │ │
│ ╲________________╱ └───────────┘ │
│ │ Yes │
│ ▼ │
│ ╱‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ txn type? ╲ │
│ ╲ ╱ │
│ ╲____________╱ │
│ │ │ │ │
│ bal withdraw deposit │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────┐ ╱‾‾‾‾‾‾╲ ╱‾‾‾‾‾‾╲ │
│ │Show│ ╱amt > 0? ╲╱amt > 0? ╲ │
│ │bal │ ╲ ╱ ╲ ╱ │
│ └─┬──┘ ╲______╱ ╲______╱ │
│ │ Yes│ │No Yes│ │No │
│ │ ▼ ▼ ▼ ▼ │
│ │ ╱‾‾‾‾‾‾‾‾╲ ┌────┐ ┌─────┐ ┌────┐ │
│ │ ╱amt<=bal? ╲│Err │ │bal │ │Err │ │
│ │ ╲ ╱ │msg │ │+=amt│ │msg │ │
│ │ ╲________╱ └─┬──┘ └──┬──┘ └─┬──┘ │
│ │ Yes│ │No │ │ │ │
│ │ ▼ ▼ │ │ │ │
│ │ ┌─────┐┌────┐ │ │ │ │
│ │ │bal ││Err │ │ │ │ │
│ │ │-=amt││msg │ │ │ │ │
│ │ └──┬──┘└─┬──┘ │ │ │ │
│ │ │ │ │ │ │ │
│ └────┴─────┴─────┴───────┴──────┴──────────────┘
Solution
def atm_session(pin, transactions, correct_pin="1234", initial_balance=1000.00):
messages = []
# PIN authentication gate
if pin != correct_pin:
messages.append("Invalid PIN. Session terminated.")
return messages
messages.append("PIN accepted.")
balance = initial_balance
# Process each transaction
for txn in transactions:
txn_type = txn[0]
if txn_type == "balance":
messages.append(f"Balance: ${balance:.2f}")
elif txn_type == "withdraw":
amount = txn[1]
if amount <= 0:
messages.append("Invalid amount.")
elif amount > balance:
messages.append(f"Insufficient funds. Balance: ${balance:.2f}")
else:
balance -= amount
messages.append(f"Withdrew ${amount:.2f}. New balance: ${balance:.2f}")
elif txn_type == "deposit":
amount = txn[1]
if amount <= 0:
messages.append("Invalid amount.")
else:
balance += amount
messages.append(f"Deposited ${amount:.2f}. New balance: ${balance:.2f}")
return messages
# Test
print(atm_session("1234", [
("balance",),
("withdraw", 200),
("balance",),
("deposit", 150),
("balance",),
]))
print(atm_session("0000", [("balance",)]))
print(atm_session("1234", [
("withdraw", 1500),
("withdraw", -50),
("withdraw", 300),
]))Why it works: This flowchart combines two patterns: a gate (PIN check at the top that blocks all further processing) and a state-machine loop (transactions that modify balance). The withdraw path has nested diamonds (valid amount? then sufficient funds?) — these become nested if/elif/else blocks. Notice how the flowchart's complexity — with many arrows converging back to the loop — flattens into clean, readable Python with early returns and elif chains. This is a key insight: flowcharts often look more complex than the equivalent code.
def atm_session(pin, transactions, correct_pin="1234", initial_balance=1000.00):
"""Simulate a full ATM session.
pin: the PIN entered by the user
transactions: list of tuples like ('balance',), ('withdraw', 200), ('deposit', 50)
Returns list of output messages.
"""
messages = []
# Implement the flowchart logic below
return messages
# Test
print(atm_session("1234", [
("balance",),
("withdraw", 200),
("balance",),
("deposit", 150),
("balance",),
]))
print(atm_session("0000", [("balance",)]))
print(atm_session("1234", [
("withdraw", 1500),
("withdraw", -50),
("withdraw", 300),
]))Expected Output
['PIN accepted.', 'Balance: $1000.00', 'Withdrew $200.00. New balance: $800.00', 'Balance: $800.00', 'Deposited $150.00. New balance: $950.00', 'Balance: $950.00']\n['Invalid PIN. Session terminated.']\n['PIN accepted.', 'Insufficient funds. Balance: $1000.00', 'Invalid amount.', 'Withdrew $300.00. New balance: $700.00']Hints
Hint 1: The flowchart has a gate at the top (PIN check) that blocks everything else. If the PIN fails, return immediately with one error message.
Hint 2: For withdrawals, validate two things: amount must be positive, and amount must not exceed balance. For deposits, just check that the amount is positive.
Implement this task scheduler flowchart. The scheduler sorts tasks by priority (1 = highest), then processes them in order. Before executing each task, it checks whether the task would exceed the time limit. If so, the task is skipped. The scheduler tracks a detailed execution log.
┌───────────┐
│ START │
└─────┬─────┘
│
▼
┌──────────────────────┐
│ Sort tasks by │
│ priority (ascending) │
│ current_time = 0 │
│ completed = [] │
│ skipped = [] │
└──────────┬───────────┘
│
┌──────── ▼ ◀───────────────────────────────────┐
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ more tasks? ╲── No ──▶ ┌────────────┐ │
│ ╲ ╱ │ Return │ │
│ ╲______________╱ │ results │ │
│ │ Yes └────────────┘ │
│ ▼ │
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ current_time + duration ╲ │
│ ╱ > time_limit? ╲ │
│ ╲ ╱ │
│ ╲__________________________╱ │
│ │ Yes │ No │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────────────┐ │
│ │ Log: │ │ Log: "Starting task" │ │
│ │ "Skip" │ │ current_time += dur │ │
│ │ Add to │ │ Log: "Completed" │ │
│ │ skipped │ │ Add to completed │ │
│ └────┬─────┘ └──────────┬───────────┘ │
│ │ │ │
│ └───────────────────┴────────────────────┘
Solution
def task_scheduler(tasks, time_limit=10):
# Sort by priority (ascending — 1 is highest priority)
sorted_tasks = sorted(tasks, key=lambda t: t["priority"])
current_time = 0
completed = []
skipped = []
log = []
for task in sorted_tasks:
name = task["name"]
priority = task["priority"]
duration = task["duration"]
# Check if task fits within time limit
if current_time + duration > time_limit:
log.append(f"t={current_time}: Skipping {name} (would exceed time limit)")
skipped.append(name)
else:
log.append(f"t={current_time}: Starting {name} (pri={priority}, dur={duration})")
current_time += duration
log.append(f"t={current_time}: Completed {name}")
completed.append(name)
return {
"completed": completed,
"skipped": skipped,
"total_time": current_time,
"log": log,
}
# Test
print(task_scheduler([
{"name": "backup", "priority": 3, "duration": 4},
{"name": "alert", "priority": 1, "duration": 2},
{"name": "report", "priority": 2, "duration": 3},
{"name": "cleanup", "priority": 4, "duration": 5},
]))
print(task_scheduler([
{"name": "quick", "priority": 1, "duration": 1},
{"name": "slow", "priority": 2, "duration": 20},
], time_limit=5))Why it works: The flowchart has a preprocessing step (sort) before the main loop — a common pattern in scheduling algorithms. Inside the loop, the timeout diamond acts as a filter: tasks that would exceed the time budget are shunted to the "skip" path instead of the "execute" path. Both paths converge back at the loop start. The key insight is that the flowchart forces you to think about both paths explicitly — what happens when you execute AND what happens when you skip. In production schedulers, this explicit branching prevents the subtle bug of silently dropping tasks.
def task_scheduler(tasks, time_limit=10):
"""Schedule and execute tasks by priority within a time limit.
tasks: list of dicts with 'name', 'priority' (1=highest), 'duration'
Returns dict with 'completed', 'skipped', 'total_time', 'log'.
"""
# Implement the flowchart logic below
pass
# Test
print(task_scheduler([
{"name": "backup", "priority": 3, "duration": 4},
{"name": "alert", "priority": 1, "duration": 2},
{"name": "report", "priority": 2, "duration": 3},
{"name": "cleanup", "priority": 4, "duration": 5},
]))
print(task_scheduler([
{"name": "quick", "priority": 1, "duration": 1},
{"name": "slow", "priority": 2, "duration": 20},
], time_limit=5))Expected Output
{'completed': ['alert', 'report', 'backup'], 'skipped': ['cleanup'], 'total_time': 9, 'log': ['t=0: Starting alert (pri=1, dur=2)', 't=2: Completed alert', 't=2: Starting report (pri=2, dur=3)', 't=5: Completed report', 't=5: Starting backup (pri=3, dur=4)', 't=9: Completed backup', 't=9: Skipping cleanup (would exceed time limit)']}\n{'completed': ['quick'], 'skipped': ['slow'], 'total_time': 1, 'log': ['t=0: Starting quick (pri=1, dur=1)', 't=1: Completed quick', 't=1: Skipping slow (would exceed time limit)']}Hints
Hint 1: Sort tasks by priority first (lowest number = highest priority). Then iterate through the sorted list, checking if each task fits within the remaining time.
Hint 2: Track current_time. Before starting each task, check if current_time + duration would exceed the time_limit. If it would, skip the task and log it.
Implement a network request system with the circuit breaker pattern. This is a real-world resilience pattern used in production systems (Netflix Hystrix, Python pybreaker).
Circuit breaker states:
- CLOSED (normal): requests go through. On failure, retry up to
max_retries. Afterfailure_thresholdtotal failures, circuit opens. - OPEN (blocking): all requests are immediately blocked. After
recovery_timeoutblocked requests, transition to HALF-OPEN. - HALF-OPEN (probing): allow ONE request through. If it succeeds, close the circuit (reset failures). If it fails, reopen.
The responses list simulates server responses in order. Each response is consumed one at a time. "wait" responses are treated the same as any other response for consumption purposes.
┌───────────┐
│ START │
└─────┬─────┘
│
▼
┌────────────────────┐
│ state = CLOSED │
│ total_failures = 0 │
│ blocked_count = 0 │
└──────────┬─────────┘
│
┌───────── ▼ ◀────────────────────────────────────────────────┐
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ more responses?╲── No ──▶ ┌──────────────────────┐ │
│ ╲ ╱ │ Return results │ │
│ ╲_____________╱ └──────────────────────┘ │
│ │ Yes │
│ ▼ │
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ state == OPEN? ╲── Yes ──▶ ┌─────────────────┐ │
│ ╲ ╱ │ blocked_count++ │ │
│ ╲_____________╱ └────────┬────────┘ │
│ │ No │ │
│ │ ▼ │
│ │ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ │ ╱ blocked >= recovery? ╲ │
│ │ ╲ ╱ │
│ │ ╲_____________________╱ │
│ │ │ Yes │ No │
│ │ ▼ ▼ │
│ │ ┌────────────┐ ┌──────────┐ │
│ │ │state = │ │Log: │ │
│ │ │ HALF-OPEN │ │"blocked" │ │
│ │ └─────┬──────┘ └────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲ ╱‾‾‾‾‾‾‾‾‾‾‾╲ │ │
│ ╱state == HALF-OPEN?╲ ╱ probe succeeds?╲ │ │
│ ╲ ╱ ╲ ╱ │ │
│ ╲________________╱ ╲____________╱ │ │
│ │ No │ Yes │Yes │No │ │
│ │ │ ▼ ▼ │ │
│ │ │ ┌──────────┐┌────────┐ │ │
│ │ │ │CLOSED, ││OPEN, │ │ │
│ │ │ │reset ││reset │ │ │
│ │ │ │failures ││blocked │ │ │
│ │ │ └────┬─────┘└───┬────┘ │ │
│ │ │ │ │ │ │
│ ▼ └────────┴──────────┴───────┘ │
│ [CLOSED state: try request with retries] │
│ │ │
│ ▼ │
│ ╱‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ response == ╲── Yes ──▶ Log "delivered" ───────────┤
│ ╲ "success"? ╱ │
│ ╲____________╱ │
│ │ No │
│ ▼ │
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ retries < max_retries?╲── Yes ──▶ Log "retry" ──────┤
│ ╲ ╱ │
│ ╲____________________╱ │
│ │ No │
│ ▼ │
│ ┌──────────────────┐ │
│ │ total_failures++ │ │
│ │ Log "failed" │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╲ │
│ ╱ failures >= threshold? ╲── Yes ──▶ state = OPEN │
│ ╲ ╱ blocked = 0 ──┤
│ ╲______________________╱ │
│ │ No │
│ └────────────────────────────────────────────┘
Solution
def circuit_breaker_request(responses, max_retries=3, failure_threshold=5,
recovery_timeout=3):
state = "CLOSED"
total_failures = 0
blocked_count = 0
results = []
request_num = 0
i = 0 # index into responses
while i < len(responses):
request_num += 1
resp = responses[i]
# --- OPEN state: block requests ---
if state == "OPEN":
blocked_count += 1
if blocked_count >= recovery_timeout:
# Transition to HALF-OPEN — but still consume this response
state = "HALF-OPEN"
results.append({
"request": request_num,
"response": resp,
"action": "circuit OPEN — waiting for recovery",
})
else:
results.append({
"request": request_num,
"response": resp,
"action": "circuit OPEN — request blocked",
})
i += 1
continue
# --- HALF-OPEN state: probe with one request ---
if state == "HALF-OPEN":
if resp == "success":
state = "CLOSED"
total_failures = 0
blocked_count = 0
results.append({
"request": request_num,
"response": resp,
"action": "HALF-OPEN probe succeeded — circuit CLOSED",
})
else:
state = "OPEN"
blocked_count = 0
results.append({
"request": request_num,
"response": resp,
"action": "HALF-OPEN probe failed — circuit OPEN",
})
i += 1
continue
# --- CLOSED state: try request with retries ---
retries = 0
while i < len(responses):
resp = responses[i]
if resp == "success":
results.append({
"request": request_num,
"response": resp,
"action": "delivered",
})
i += 1
break
# Failure — can we retry?
retries += 1
if retries <= max_retries:
results.append({
"request": request_num,
"response": resp,
"action": f"retry (attempt {retries})",
})
i += 1
else:
# Exhausted retries
total_failures += 1
results.append({
"request": request_num,
"response": resp,
"action": "failed after max retries",
})
i += 1
# Check if we should open the circuit
if total_failures >= failure_threshold:
state = "OPEN"
blocked_count = 0
break
return {
"results": results,
"final_state": state,
"total_failures": total_failures,
}
# Test — normal operation with retries
print(circuit_breaker_request(
["timeout", "timeout", "success", "success"]
))
# Test — circuit opens after threshold
print(circuit_breaker_request(
["error", "error", "error", "error", "error", "success", "success", "success"]
))
# Test — circuit opens, then recovers via half-open
print(circuit_breaker_request(
["error", "error", "error", "error", "error",
"wait", "wait", "wait",
"success",
"success"]
))Why it works: The circuit breaker is a three-state machine, and the flowchart makes the transitions explicit. In CLOSED state, the inner retry loop consumes responses until success or retry exhaustion. When failures hit the threshold, the state transitions to OPEN. In OPEN state, requests are blocked and a counter ticks up. After recovery_timeout blocked requests, the state moves to HALF-OPEN. In HALF-OPEN, exactly one request is probed — success closes the circuit, failure reopens it. This pattern is used in every major distributed system (Netflix, AWS, Google) to prevent cascading failures. The flowchart forces you to think about every state transition, which is exactly what makes resilient systems reliable.
def circuit_breaker_request(responses, max_retries=3, failure_threshold=5,
recovery_timeout=3):
"""Simulate network requests with circuit breaker pattern.
responses: list of 'success', 'timeout', or 'error' strings
representing server responses in order.
The circuit breaker has 3 states: CLOSED, OPEN, HALF-OPEN.
Returns dict with 'results', 'final_state', 'total_failures'.
"""
# Implement the flowchart logic below
pass
# Test — normal operation with retries
print(circuit_breaker_request(
["timeout", "timeout", "success", "success"]
))
# Test — circuit opens after threshold
print(circuit_breaker_request(
["error", "error", "error", "error", "error", "success", "success", "success"]
))
# Test — circuit opens, then recovers via half-open
print(circuit_breaker_request(
["error", "error", "error", "error", "error",
"wait", "wait", "wait",
"success",
"success"]
))Expected Output
{'results': [{'request': 1, 'response': 'timeout', 'action': 'retry (attempt 1)'}, {'request': 1, 'response': 'timeout', 'action': 'retry (attempt 2)'}, {'request': 1, 'response': 'success', 'action': 'delivered'}, {'request': 2, 'response': 'success', 'action': 'delivered'}], 'final_state': 'CLOSED', 'total_failures': 2}\n{'results': [{'request': 1, 'response': 'error', 'action': 'retry (attempt 1)'}, {'request': 1, 'response': 'error', 'action': 'retry (attempt 2)'}, {'request': 1, 'response': 'error', 'action': 'retry (attempt 3)'}, {'request': 1, 'response': 'error', 'action': 'failed after max retries'}, {'request': 2, 'response': 'error', 'action': 'circuit OPEN — request blocked'}, {'request': 3, 'response': 'success', 'action': 'circuit OPEN — request blocked'}, {'request': 4, 'response': 'success', 'action': 'circuit OPEN — request blocked'}, {'request': 5, 'response': 'success', 'action': 'circuit OPEN — request blocked'}], 'final_state': 'OPEN', 'total_failures': 5}\n{'results': [{'request': 1, 'response': 'error', 'action': 'retry (attempt 1)'}, {'request': 1, 'response': 'error', 'action': 'retry (attempt 2)'}, {'request': 1, 'response': 'error', 'action': 'retry (attempt 3)'}, {'request': 1, 'response': 'error', 'action': 'failed after max retries'}, {'request': 2, 'response': 'error', 'action': 'circuit OPEN — request blocked'}, {'request': 3, 'response': 'wait', 'action': 'circuit OPEN — request blocked'}, {'request': 4, 'response': 'wait', 'action': 'circuit OPEN — request blocked'}, {'request': 5, 'response': 'wait', 'action': 'circuit OPEN — waiting for recovery'}, {'request': 6, 'response': 'success', 'action': 'HALF-OPEN probe succeeded — circuit CLOSED'}, {'request': 7, 'response': 'success', 'action': 'delivered'}], 'final_state': 'CLOSED', 'total_failures': 5}Hints
Hint 1: Track three things: circuit state (CLOSED/OPEN/HALF-OPEN), consecutive failure count, and a "blocked counter" for how many requests have been blocked while OPEN. After recovery_timeout blocked requests, transition to HALF-OPEN.
Hint 2: In CLOSED state: on failure, retry up to max_retries, then increment total failures. When total failures hit the threshold, open the circuit. In OPEN state: block requests and count them. After recovery_timeout blocked requests, move to HALF-OPEN. In HALF-OPEN: if the next request succeeds, close the circuit and reset failures. If it fails, reopen.
