Problem Solving: Beginner

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:
1bill = 85.50
2tip_percent = 18
3
4tip_amount = bill * tip_percent / 100
5total = bill + tip_amount
6
7print(f"Tip: ${tip_amount:.2f}")
8print(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
INPUT: A list of numbers
The raw data you start with, e.g. [5, 2, 9, 1, 7]
OUTPUT: A single number
OUTPUT: A single number
The result you need to produce: the largest value (9)
PROCESS: Compare to find max
PROCESS: Compare to find max
The transformation logic that turns input into output
With this analysis, writing the code becomes straightforward:
1numbers = [5, 2, 9, 1, 7]
2
3largest = numbers[0]
4for num in numbers:
5 if num > largest:
6 largest = num
7
8print(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:
1x = 5
2y = 3
3x = x + y
4y = x - y
5x = 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.

1x = 5
2y = 3
3x = x + y
4y = x - y
5x = x - y
6print(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:
1num = 123
2total = 0
3while num > 0:
4 digit = num % 10
5 total = total + digit
6 num = num // 10
Start: num=123, total=0
Start: num=123, total=0
Initialize variables before the loop begins
Iteration 1: digit=3, total=3
Iteration 1: digit=3, total=3
123 % 10 = 3, total goes from 0 to 3, num becomes 12
Iteration 2: digit=2, total=5
Iteration 2: digit=2, total=5
12 % 10 = 2, total goes from 3 to 5, num becomes 1
Iteration 3: digit=1, total=6
Iteration 3: digit=1, total=6
1 % 10 = 1, total goes from 5 to 6, num becomes 0
Loop ends: num is 0
Loop ends: num is 0
The while condition fails, final total is 6
1num = 123
2total = 0
3while num > 0:
4 digit = num % 10
5 total = total + digit
6 num = num // 10
7print(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
2numbers = [4, 7, 2, 9]
3total = 0
4for num in numbers:
5 total = total + num
6print(f"Sum: {total}")
>>>Output
Sum: 22
1# Count items matching a condition
2words = ["apple", "banana", "cherry", "apricot"]
3count = 0
4for word in words:
5 if word.startswith("a"):
6 count = count + 1
7print(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.
1def contains_negative(numbers):
2 for num in numbers:
3 if num < 0:
4 return True
5 return False
6
7print(contains_negative([5, 3, -2, 8]))
8print(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.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3evens = []
4for num in numbers:
5 if num % 2 == 0:
6 evens.append(num)
7
8print(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.

Prints [1, 3, 5] instead of [2, 4, 6]

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:
1def find_max(numbers):
2 if not numbers:
3 return None
4 largest = numbers[0]
5 for num in numbers:
6 if num > largest:
7 largest = num
8 return largest
9
10# Test cases
11print(find_max([]))
12print(find_max([5]))
13print(find_max([3, 7]))
14print(find_max([4, 2, 9, 1, 7]))
15print(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

Think before you code

Category
Python
Difficulty
beginner
Duration
20 minutes
Challenges
0 hands-on challenges

Topics covered: Breaking Problems into Steps, Input/Output Analysis, Tracing Code Manually, Pattern Recognition, Testing Simple Cases

Lesson Sections

  1. Breaking Problems into 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: 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

  2. Input/Output Analysis

    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

  3. Tracing Code Manually

    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

  4. Pattern Recognition

    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

  5. Testing Simple Cases

    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