Stripe's engineering culture is famous for its focus on algorithmic problem-solving because every major product at the company - payments, billing, fraud detection, and tax calculation - was built by engineers who learned to decompose complex problems into small, solvable steps before writing a single line of code. The ability to look at a hard problem and see a structured sequence of simpler problems is what separates engineers who ship at scale from those who get stuck. Every Stripe engineer started exactly where you are now, practicing the skill of translating a real-world problem into a clear algorithm. This lesson builds exactly that foundation.
Breaking Problems into Steps
Daily Life
Interviews
Turn word problems into code steps
Every complex problem is just a collection of simple problems. The key skill is decomposition: breaking a big problem into smaller, manageable pieces that you can solve one at a time.
Example: Calculating a Tip
Problem: Write a program that calculates the tip for a restaurant bill.
Before writing any code, let's break this down:
01
Get bill amount
Collect the total bill as input from the user
02
Get tip percent
Collect the desired tip percentage
03
Calculate tip
Multiply bill by percentage divided by 100
04
Calculate total
Add the tip amount to the original bill
05
Display results
Print the tip and total for the user
Now each step is simple enough to code directly:
1
bill=85.50
2
tip_percent=18
3
4
tip_amount=bill*tip_percent/100
5
total=bill+tip_amount
6
7
print(f"Tip: ${tip_amount:.2f}")
8
print(f"Total: ${total:.2f}")
>>>Output
Tip: $15.39
Total: $100.89
Notice how the code directly mirrors our steps. This is no accident. When you plan well, the code practically writes itself.
The Power of Pseudocode
Pseudocode is a way of writing out your solution in plain language before converting it to real code. It helps you focus on logic without worrying about syntax.
•Pseudocode
FOR each number in the list
IF number is even
add it to the sum
PRINT the sum
•Python
for num in numbers:
if num % 2 == 0:
total += num
print(total)
Write pseudocode in whatever language feels natural to you. The goal is clarity, not formality. If you can follow your pseudocode, you can convert it to Python.
Fill in the Blank
> A loop adds up numbers [10, 20, 30] into a running total. Pick the right assignment operator to accumulate the sum step by step.
total = 0
for num in [10, 20, 30]:
total num
print(total)
Decomposing a problem into plain-English steps before writing code is the single most effective habit you can build as a programmer. When you can narrate each step clearly, the code almost writes itself.
Pseudocode is not a formal language. Write it in whatever way makes the logic obvious to you. The goal is clarity about the algorithm, not syntactic correctness.
TIP
If you feel stuck on a coding problem, stop writing code and write pseudocode instead. Describing the steps in plain English often reveals the solution immediately.
Input/Output Analysis
Daily Life
Interviews
Map inputs to outputs before coding
Before solving any problem, you must understand two things: What data goes IN (inputs) and what data comes OUT (outputs). Everything else is just the transformation between them.
INPUTOUTPUTPROCESS
INPUT
Starting data
What info do I begin with
OUTPUT
Target result
What do I need to produce
PROCESS
Transformation
How to turn input to output
Finding the Largest Value
Problem: Given a list of numbers, find the largest one.
INPUT: A list of numbers
The raw data you start with, e.g. [5, 2, 9, 1, 7]
OUTPUT: A single number
The result you need to produce: the largest value (9)
PROCESS: Compare to find max
The transformation logic that turns input into output
With this analysis, writing the code becomes straightforward:
1
numbers=[5,2,9,1,7]
2
3
largest=numbers[0]
4
fornuminnumbers:
5
ifnum>largest:
6
largest=num
7
8
print(f"Largest: {largest}")
>>>Output
Largest: 9
Testing with Examples
Always create concrete examples before coding. Work through them by hand to verify your understanding.
Problem: Reverse a string.
"hello""Python""a"""
"hello"
Normal case
Reverses to "olleh"
"Python"
Mixed case
Reverses to "nohtyP"
"a"
Single char
Returns "a" unchanged
""
Empty string
Returns "" with no work
These examples help you understand the pattern and catch edge cases (single character, empty string) before you write any code.
Debug Challenge
> This find_largest function initializes largest to 0, so it fails when all numbers in the list are negative. It returns 0 instead of the actual largest value.
Returns 0 instead of -1 for a list of negative numbers
Analyzing inputs and outputs before writing code reveals hidden assumptions. The fix here -- initializing largest to numbers[0] rather than 0 -- only becomes obvious when you test with negative inputs.
Concrete examples with specific values are your best tool for discovering these assumptions. Work through the input by hand before writing any code, and your implementation will be far more robust.
TIP
When writing a function, always ask: what is the smallest meaningful input? What happens if every value is negative? Zero? Identical? Thinking through these cases before coding prevents whole categories of bugs.
Tracing Code Manually
Daily Life
Interviews
Trace any loop by hand on paper
Code tracing means executing code in your head (or on paper) exactly as a computer would, step by step. This skill is essential for debugging and understanding how code works.
The Variable Table Method
Create a table tracking the value of each variable after every line executes. Let's trace this code:
1
x=5
2
y=3
3
x=x+y
4
y=x-y
5
x=x-y
01
x = 5
After line 1: x is assigned 5
02
x = 5, y = 3
After line 2: y is assigned 3
03
x = 8, y = 3
After line 3: x becomes 5 + 3 = 8
04
x = 8, y = 5
After line 4: y becomes 8 - 3 = 5
05
x = 3, y = 5
After line 5: x becomes 8 - 5 = 3, swap complete
This code swaps the values of x and y without using a temporary variable. Tracing reveals how it works.
1
x=5
2
y=3
3
x=x+y
4
y=x-y
5
x=x-y
6
print(f"x = {x}, y = {y}")
>>>Output
x = 3, y = 5
Tracing Loops
Loops require tracking values across multiple iterations. Trace this code that calculates the sum of digits:
1
num=123
2
total=0
3
whilenum>0:
4
digit=num%10
5
total=total+digit
6
num=num//10
Start: num=123, total=0
Initialize variables before the loop begins
Iteration 1: digit=3, total=3
123 % 10 = 3, total goes from 0 to 3, num becomes 12
Iteration 2: digit=2, total=5
12 % 10 = 2, total goes from 3 to 5, num becomes 1
Iteration 3: digit=1, total=6
1 % 10 = 1, total goes from 5 to 6, num becomes 0
Loop ends: num is 0
The while condition fails, final total is 6
1
num=123
2
total=0
3
whilenum>0:
4
digit=num%10
5
total=total+digit
6
num=num//10
7
print(f"Sum of digits: {total}")
>>>Output
Sum of digits: 6
TIP
When your code doesn't work, trace it by hand with a specific input. You'll often find the bug by seeing where your expectation differs from what actually happens.
Fill in the Blank
> A loop runs four times with i going from 0 to 3, combining each value into a running total. Pick an arithmetic operator and trace what the final result will be.
total = 0
for i in range(4):
total = total i
print(total)
Manual tracing builds the mental model you need to debug. When code does not produce the expected output, tracing through it step by step reveals exactly where your assumption diverges from what the computer actually does.
The variable table method is also invaluable for understanding unfamiliar code. Tracing someone else's loop line by line is often faster than reading their explanation.
TIP
Practice tracing code that you did not write. Open any short Python snippet, create a blank table with columns for each variable, and fill in values row by row. After a few sessions this skill becomes automatic.
Pattern Recognition
Daily Life
Interviews
Pick the right pattern for any loop
Most programming problems follow common patterns. Once you recognize a pattern, you can apply a known solution approach. This is why experienced programmers solve problems faster; they've seen similar patterns before.
The Accumulator Pattern
This pattern builds up a result by processing items one at a time. You start with an initial value and update it in a loop.
1
# Sum all numbers
2
numbers=[4,7,2,9]
3
total=0
4
fornuminnumbers:
5
total=total+num
6
print(f"Sum: {total}")
>>>Output
Sum: 22
1
# Count items matching a condition
2
words=["apple","banana","cherry","apricot"]
3
count=0
4
forwordinwords:
5
ifword.startswith("a"):
6
count=count+1
7
print(f"Words starting with 'a': {count}")
>>>Output
Words starting with 'a': 2
These three patterns cover the vast majority of beginner problems. Recognizing which one applies is often the hardest part, so here is a quick reference to help you decide.
Pattern Selection Checklist
Need a single total, count, or combined result? Use the accumulator pattern
Need to know IF something exists in a collection? Use the search pattern with early return
Need a subset of items that match a rule? Use the filter pattern with an empty list and append
Not sure yet? Start with the simplest approach, then refactor once you see the shape of the answer
The Search Pattern
This pattern looks for something in a collection, returning early when found.
1
defcontains_negative(numbers):
2
fornuminnumbers:
3
ifnum<0:
4
returnTrue
5
returnFalse
6
7
print(contains_negative([5,3,-2,8]))
8
print(contains_negative([5,3,2,8]))
>>>Output
True
False
The Filter Pattern
This pattern creates a new collection containing only items that match a condition.
1
numbers=[1,2,3,4,5,6,7,8,9,10]
2
3
evens=[]
4
fornuminnumbers:
5
ifnum%2==0:
6
evens.append(num)
7
8
print(f"Even numbers: {evens}")
>>>Output
Even numbers: [2, 4, 6, 8, 10]
Debug Challenge
> This filter loop uses != 0 instead of == 0 in the modulo check, so it keeps odd numbers instead of even ones.
The accumulator, search, and filter patterns cover the vast majority of beginner-level problems. Once you can identify which pattern fits, you already know the structure of the solution before you write a single line.
More complex algorithms are usually combinations of these three patterns. A two-pass algorithm might filter the data in the first pass and accumulate a result in the second. Recognizing the components makes even advanced code approachable.
TIP
Before writing code, ask: Am I building a single result (accumulator)? Am I looking for something that exists (search)? Am I keeping a subset (filter)? The answer shapes your entire approach.
Testing Simple Cases
Daily Life
Interviews
Catch bugs with edge case tests
Before testing complex inputs, always verify your code works with simple cases. If it fails on easy inputs, it will definitely fail on hard ones.
Start Simple
When testing a function that processes lists, try these in order:
01
Empty list: []
Does the function handle having no data at all?
02
Single: [5]
Does it work when there is only one element?
03
Two: [3, 7]
The simplest case with an actual comparison
04
Normal: [1,2,3,4,5]
A typical case to verify basic correctness
05
Edge cases
Negative numbers, duplicates, already sorted
Example: Testing a function that finds the maximum:
1
deffind_max(numbers):
2
ifnotnumbers:
3
returnNone
4
largest=numbers[0]
5
fornuminnumbers:
6
ifnum>largest:
7
largest=num
8
returnlargest
9
10
# Test cases
11
print(find_max([]))
12
print(find_max([5]))
13
print(find_max([3,7]))
14
print(find_max([4,2,9,1,7]))
15
print(find_max([-3,-7,-1,-5]))
>>>Output
None
5
7
9
-1
Edge Cases Matter
Edge cases are inputs at the boundaries of what's valid. They're where bugs most commonly hide.
EmptySingleBoundaryDuplicatesSortedReversed
Empty
No Elements
Empty list or zero input
Single
One Element
Only one item to process
Boundary
Edge Values
Min, max, or zero values
Duplicates
Repeated Data
Identical values present
Sorted
Pre-Ordered
Already in sorted order
Reversed
Flipped Order
Worst case for sorting
Handling edge cases is not optional. It separates working code from production-ready code.
TIP
A function that only works on "normal" inputs is broken. Professional code handles edge cases gracefully.
The consequences of ignoring edge cases can be enormous.
Debug Challenge
> This average calculator divides by len(numbers) without checking for an empty list first, causing a ZeroDivisionError when no numbers are provided.
ZeroDivisionError: division by zero when the list is empty
Testing with simple cases first -- empty, single element, two elements -- catches most bugs before you even reach complex inputs. Build this habit and you will spend far less time debugging later.
Edge cases are where the gap between "working code" and "production-ready code" lives. A function that handles only normal inputs is incomplete, no matter how elegantly it handles those cases.
TIP
Write your test cases before your code, not after. Thinking about what the output should be for an empty list, a single item, and a list of negatives forces you to understand the problem fully before you implement anything.
Breaking down problems into clear, logical steps is the most important skill in programming. Put these fundamentals to the test with hands-on challenges in the Python Builder.
❯❯❯PUTTING IT ALL TOGETHER
> You are a junior data analyst at Accenture working through your first real-world client data cleaning task. You must systematically debug errors in a messy CSV dataset to deliver a clean, validated output file by end of week.
Breaking the problem into steps (read, validate, clean, output) converts an overwhelming messy dataset into a manageable sequence of small, verifiable actions.
Tracing code manually with a variable table reveals exactly how each cleaning transformation changes a value before running it against 50,000 rows.
Pattern recognition identifies that repeated KeyError exceptions all share the same missing column name, targeting the root cause in one fix.
Testing simple cases first verifies each cleaning function works on known good and bad inputs before applying it to the full dataset.
KEY TAKEAWAYS
Break complex problems into small, manageable steps
Always identify INPUTS and OUTPUTS before coding
Trace code by hand using a variable table to understand behavior
Recognize common patterns: accumulator, search, filter
Test with simple cases first, then edge cases
Write pseudocode to plan your logic before writing Python
Every complex problem is just a collection of simple problems. The key skill is decomposition: breaking a big problem into smaller, manageable pieces that you can solve one at a time. Example: Calculating a Tip Problem: Write a program that calculates the tip for a restaurant bill. Before writing any code, let's break this down: Now each step is simple enough to code directly: Notice how the code directly mirrors our steps. This is no accident. When you plan well, the code practically writes its
Before solving any problem, you must understand two things: What data goes IN (inputs) and what data comes OUT (outputs). Everything else is just the transformation between them. Finding the Largest Value Problem: Given a list of numbers, find the largest one. With this analysis, writing the code becomes straightforward: Testing with Examples Always create concrete examples before coding. Work through them by hand to verify your understanding. Problem: Reverse a string. These examples help you u
Code tracing means executing code in your head (or on paper) exactly as a computer would, step by step. This skill is essential for debugging and understanding how code works. The Variable Table Method Create a table tracking the value of each variable after every line executes. Let's trace this code: Tracing Loops Loops require tracking values across multiple iterations. Trace this code that calculates the sum of digits: Manual tracing builds the mental model you need to debug. When code does n
Most programming problems follow common patterns. Once you recognize a pattern, you can apply a known solution approach. This is why experienced programmers solve problems faster; they've seen similar patterns before. The Accumulator Pattern This pattern builds up a result by processing items one at a time. You start with an initial value and update it in a loop. These three patterns cover the vast majority of beginner problems. Recognizing which one applies is often the hardest part, so here is
Before testing complex inputs, always verify your code works with simple cases. If it fails on easy inputs, it will definitely fail on hard ones. Start Simple When testing a function that processes lists, try these in order: Example: Testing a function that finds the maximum: Edge Cases Matter Edge cases are inputs at the boundaries of what's valid. They're where bugs most commonly hide. Handling edge cases is not optional. It separates working code from production-ready code. The consequences o