Skip to content

Computational Thinking

Computational thinking is the foundation skill beneath all of computer science, because it is the art of “turning a still-fuzzy problem into a specification clear enough to act on.” And here is the twist: that exact same skill instantly makes you better at directing an AI — because an AI, just like a computer, needs a clear specification to do good work.

Computational thinking is the thought process of defining a problem and framing its solution in a form that some “processor” — whether a human, a computer, or an AI — can follow and carry out.

The most important point: computational thinking is not coding. Coding is just one tool at the end of the pipeline. Computational thinking is the way of thinking that happens before you even touch the keyboard.

You already use computational thinking every day without noticing — when you plan a route, sequence your chores, or hunt for something in a messy closet. What this course does is make that thinking explicit, systematic, and reusable.

At its heart, it is about clearly separating:

  • What is the input we have
  • What is the output we want
  • What steps take us from input to output without any ambiguity

Computational thinking is made of four sub-skills that work together. We will carry one running example through all four: “sort a messy bookshelf into order.”

Definition: Breaking a big, tangled problem into smaller sub-problems you can tackle one at a time.

“Sort the bookshelf” sounds big and vague. Decomposed, it becomes:

  1. Pull every book off and pile them together
  2. Decide what to sort by (author? genre? color?)
  3. Split the books into groups by that criterion
  4. Order the books within each group
  5. Put the groups back on the shelf in order

Once decomposed, each sub-step is “small enough” that you immediately know what to do. That is the signal you have decomposed well enough.

Judging AI with this pillar: When an AI hands you a solution, ask whether it decomposed the problem into the right sub-steps — did it silently skip an edge case (empty input? duplicate values? a tie?) because that sub-step never got named?

Definition: Spotting similarities or repeated structure so you can reuse one solution instead of inventing a new one each time.

While sorting books, you notice that “ordering within a group” is the same pattern for every group — whether it is the novels or the textbooks, alphabetical A-to-Z works identically.

That means you don’t invent a new method per group. You design “order alphabetically” once and reuse it on every group.

Seeing that “this is just like the thing I already solved” is the heart of writing reusable functions in programming.

Judging AI with this pillar: Ask whether the AI’s code reuses a well-known, well-tested pattern (a standard binary search, a standard hash-map lookup) or invents something novel from scratch — novel is a red flag, not a compliment, in code that has known-good patterns already.

Definition: Filtering out the irrelevant details and keeping only what matters for the problem.

If the criterion is “sort by author name,” the only thing that matters is the author name. Cover color, weight, paper smell, the year you bought it — all details you can throw away, because they are irrelevant to ordering.

Abstraction reduces each book to a single value, “author name,” and lets you work with that value instead of fussing over every property of the book.

Abstraction answers the question “what actually matters for this problem?” Every time you create a variable or design a data structure, you are making an abstraction decision.

Judging AI with this pillar: Check what the AI’s chosen representation keeps and drops. Did it discard a field you actually needed later (e.g., dropped the timestamp you’ll need for tie-breaking), or keep bloat that makes the code harder to reason about?

Definition: Arranging clear, repeatable, unambiguous steps that solve the problem from start to finish.

Take the results of the previous three pillars and arrange them into steps you can actually follow:

1. Take every book off and pile them up
2. For each book: read the author name (abstraction)
3. Split books into groups by the author's first letter (decomposition)
4. For each group: order alphabetically A→Z (the reused pattern)
5. Put groups on the shelf in order A to Z

A good algorithm states “what to do, in what order, and when to stop” without guessing. Hand these steps to another person (or an AI) and the result should come out the same.

Algorithm design is where all the thinking crystallizes into a repeatable “recipe,” and where we start to care about efficiency. For example, ordering books by comparing every pair takes about O(n²) time, while a smarter method can bring it down to O(n log n).

Judging AI with this pillar: Trace the AI’s steps on a tiny example by hand. Does it terminate on every input? What’s its Big-O — and is that hiding inside a helper function you didn’t examine?

From recipe to trace: watching an algorithm run

Section titled “From recipe to trace: watching an algorithm run”

Computational thinking earns its keep the moment you turn a plan into an algorithm precise enough that a machine — or a classmate — can run it without asking you a single clarifying question. CLRS opens with almost exactly this definition: an algorithm is any well-defined computational procedure that takes some value, or set of values, as input and produces some value, or set of values, as output (Cormen et al., Ch. 1). Notice this matches the four pillars exactly: decomposition gave us the steps, abstraction gave us the values, pattern recognition gave us the reusable step, and algorithm design is the well-defined ordering of it all.

Let’s watch one run, one comparison at a time: sorting the list [5, 3, 8, 1] with bubble sort — repeatedly compare neighbors and swap if they’re out of order.

def bubble_sort(nums: list[int]) -> list[int]:
n = len(nums)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if nums[j] > nums[j + 1]:
nums[j], nums[j + 1] = nums[j + 1], nums[j]
swapped = True
if not swapped:
break # already sorted — no need to keep looping
return nums

Trace it by hand on [5, 3, 8, 1]:

Pass Compare Swap? List after
1 5, 3 yes [3, 5, 8, 1]
1 5, 8 no [3, 5, 8, 1]
1 8, 1 yes [3, 5, 1, 8]
2 3, 5 no [3, 5, 1, 8]
2 5, 1 yes [3, 1, 5, 8]
2 5, 8 no [3, 1, 5, 8]
3 3, 1 yes [1, 3, 5, 8]
3 3, 5 no [1, 3, 5, 8]

Sorted in 3 passes. Notice the pillars in action: decomposition split “sort the list” into repeated passes; pattern recognition reused the same compare-and-swap step every single time; abstraction meant the algorithm never cared what the numbers represented — prices, scores, ages — only their relative order; algorithm design is the precise nesting of loops that guarantees termination.

Play with the widget above and pause on any step — it is running exactly the trace in the table.

Bubble sort’s outer-inner loop structure means, in the worst case, it makes roughly comparisons for a list of n items. That’s fine for 4 books or 4 numbers. It stops being fine fast:

n O(n) O(n log n) O(n²)
10 10 ~33 100
1,000 1,000 ~9,966 1,000,000
1,000,000 1,000,000 ~19,931,569 1,000,000,000,000

At a million items, an O(n²) recipe needs roughly a trillion operations — minutes to hours on real hardware — while an O(n log n) recipe (like merge sort, or Python’s built-in sorted(), which uses Timsort) finishes in a fraction of a second.

This is the other half of algorithm design: it’s not enough for the recipe to be correct — it has to be correct and cheap enough for the n you actually have. A correct O(n²) recipe that never finishes in production is, practically speaking, wrong.

The core skill of computational thinking is turning a vague request into a precise specification — and the striking part is that a precise specification is a great prompt for an AI.

Compare them side by side:

Vague request Precise spec / prompt
“Help me organize the student data” “Sort the student list from this CSV by exam score, highest first; break ties by last name A→Z; export a CSV with columns name, score, rank.”
“Write some code to do math” “Write a Python function average(nums: list[float]) -> float that returns the mean. If the list is empty, return 0.0.”
“Summarize this article” “Summarize this article into 3 bullet points, each at most 2 sentences, focusing on the conclusion and key numbers. Write in English.”

Notice that both a human and an AI answer the right-hand requests better, every time, because they specify all three things: the input (what data), the output (what’s wanted, in what format), and the edge conditions (tie-breaks, empty list). Those are the direct output of computational thinking.

A vague request forces the listener to guess what you want — and guessing is the root cause of wrong-scope results. Writing a good specification removes the guesswork entirely.

Let’s solve a genuinely ambiguous task end to end: “Help me schedule my study sessions for exams.”

This is very vague — exams for which subjects? How much time? How many hours a day can you study? We will walk it through all four pillars.

Decomposition: Break it into the sub-questions we must answer first.

  • Which subjects do I have to take, and on which day is each exam?
  • How hard is each subject / how many hours does it need?
  • How many free hours do I have each day?
  • How many total days remain before the first exam?

Abstraction: Cut what’s irrelevant; keep only what’s needed. We reduce each subject to just three values.

subjects = [
{"name": "Calculus", "exam_day": 5, "hours_needed": 8},
{"name": "Physics", "exam_day": 7, "hours_needed": 6},
{"name": "Programming", "exam_day": 3, "hours_needed": 4},
]
hours_per_day = 3 # free hours per day

(Book cover colors, your mood while studying, your favorite café — all thrown away.)

Pattern recognition: Notice this is the same problem as “allocate a limited resource (time) to tasks with deadlines (exam days)” — the same shape as job scheduling or delivery routing. A rule that works well: “the task with the nearest deadline goes first.”

Algorithm design: Assemble everything into followable steps.

1. Sort subjects by exam day, nearest first
2. For each remaining day before the exams:
a. Find the subject with the nearest deadline that still needs hours
b. Allocate that day's free hours to that subject
c. Reduce that subject's remaining hours
3. If any subject can't be finished before its exam → warn to add time / cut scope

Sorting the subjects by exam day up front costs O(n log n); the day-by-day allocation loop costs O(days) — cheap enough to run instantly even with a full semester of subjects.

Now a problem that had no clear answer has become a specification precise enough to code right away — or to hand to an AI to code for you.

Practice decomposition and abstraction yourself before peeking.

1. Decompose “make an omelet for your sibling” into at least 6 clear sub-steps, where each step is unambiguous.

Solution
1. Crack 2 eggs into a bowl
2. Add a pinch of salt and beat until fully uniform
3. Heat a pan on medium, add 2 tbsp oil, wait until the oil is hot
4. Pour the eggs into the pan
5. When the edges start to turn golden (about 1 minute), flip to the other side
6. Wait 30 more seconds, then plate it

Key point: every step has a quantity and a clear “when do I stop” signal — not just “fry the eggs.”

2. You must find a friend named “Pim” in a LINE group of 500 members. Do the abstraction: which member data is necessary to search, and which can be discarded?

Solution

Necessary: the display name only, because the search criterion is the name.

Discardable: profile photo, online status, join date, message count, theme color, and so on.

Lesson: good abstraction reduces “search a 500-member group” down to “compare name strings.”

3. “Sort the 5 cards in your hand from low to high” — what reusable pattern is hidden in this, applicable to ordering other things?

Solution

The pattern is “compare items and slide each one into its correct position” (exactly how people sort cards in hand — pick up one card and insert it where it belongs — this is Insertion Sort).

The pattern reuses across sorting numbers, names, scores — only the “comparison criterion” changes while the structure stays the same. This is why programming languages have a sort() that accepts a comparison criterion as an argument.

4. Rewrite the vague request “help me name my photo files neatly” into a specification precise enough to act on or hand to an AI.

Solution

An example of a precise specification:

“Rename every image file in this folder to the format YYYY-MM-DD_NNN.jpg, using the capture date from the file’s metadata, ordered by capture time from oldest to newest — e.g. 2026-06-30_001.jpg.”

It covers input (files in folder + metadata), output (the name format), and condition (ordered by time).

5. Look at the <algo-sort> widget above with its bars. If the input were already reverse-sorted (worst case) and had 8 items, how many pairwise comparisons would bubble sort make in total, and what’s that in Big-O notation?

Solution

Pass 1 compares 7 pairs, pass 2 compares 6, pass 3 compares 5, … down to pass 7 comparing 1 pair: 7+6+5+4+3+2+1 = 28 comparisons for n = 8.

In general that sum is n(n-1)/2, which grows proportional to — so bubble sort’s worst case is O(n²).

6. This AI-generated “bubble sort” has a bug. Find it and explain why it fails.

def bubble_sort_buggy(nums: list[int]) -> list[int]:
n = len(nums)
for i in range(n):
for j in range(n):
if nums[j] > nums[j + 1]:
nums[j], nums[j + 1] = nums[j + 1], nums[j]
return nums
Solution

The inner loop runs j all the way up to n - 1, so on the last iteration it accesses nums[j + 1], which is nums[n] — out of range. This raises an IndexError.

Fix: the inner range must stop one short of the current unsorted boundary: for j in range(n - 1 - i): (as in the correct version above). This is exactly a decomposition/algorithm-design bug — the loop’s stopping condition was never precisely specified.

7. Your signup form must reject a username if it’s already taken, checked against 1,000,000 existing usernames, on every keystroke. Which data structure should back that check, and why?

Solution

A hash set (Python set). Lookup is O(1) on average, so checking against a million usernames is instant. A plain list would need O(n) — scanning up to a million entries per keystroke, which is exactly the kind of naive choice that “works” on a demo of 10 usernames and melts down in production.

8. Trace the earliest-deadline-first scheduler from the Real-World Problem section by hand on this input, and predict how many hours are spent on each subject, each day.

subjects = [
{"name": "Chemistry", "last_day": 1, "hours_needed": 3},
{"name": "History", "last_day": 3, "hours_needed": 4},
]
hours_per_day = 3
Solution
Day Subject studied Hours used Remaining after
1 Chemistry (nearest deadline) 3 Chemistry: 0, History: 4
2 History (only one left) 3 History: 1
3 History 1 (2 hours free) History: 0

Chemistry finishes exactly on its last usable day; History finishes a day early with 2 hours to spare on Day 3. This is the greedy “earliest deadline first” rule in action — always spend today’s hours on whichever unfinished subject is closest to running out of time.

Suppose you send this vague prompt to an AI:

“Write a function to find the largest number.”

The AI might respond like this:

def largest(a, b):
return a if a > b else b

This code “works,” but it is wrong-scope — you may have intended to find the maximum across a list of any number of values, yet the AI guessed you meant “two numbers” because the prompt never said otherwise. The problem isn’t that the AI is dumb; it’s that the spec left room to guess.

The fix is to tighten the spec with the four pillars: state the input (a list of numbers), the output (one maximum value), and the edge condition (what happens with an empty list).

Solution

The tightened prompt:

“Write a Python function largest(nums: list[float]) -> float that returns the largest value in the list. If the list is empty, raise ValueError. Do not use the language’s built-in max().”

The right-scope result:

def largest(nums: list[float]) -> float:
if not nums:
raise ValueError("the list must contain at least one item")
biggest = nums[0]
for n in nums[1:]:
if n > biggest:
biggest = n
return biggest

Lesson: a good prompt is a good specification, and a good specification comes from computational thinking. Every time an AI answers off-target, first ask yourself: “where did I leave room for it to guess?”

Round 2: when “it works” isn’t enough

Section titled “Round 2: when “it works” isn’t enough”

Suppose you prompt:

“Sort this list of one million transaction IDs.”

A plausible AI answer:

def sort_transactions(ids):
n = len(ids)
for i in range(n):
for j in range(n - 1):
if ids[j] > ids[j + 1]:
ids[j], ids[j + 1] = ids[j + 1], ids[j]
return ids

This is correct — it will eventually produce a sorted list. But look at the table from the efficiency section above: at n = 1,000,000, an O(n²) bubble sort needs on the order of a trillion comparisons. On real hardware that’s minutes to hours; in a request handler with a timeout, it never returns. The AI optimized for “produces the right answer” and never surfaced “at what cost” — because the prompt never asked.

Solution

Tightened prompt:

“Sort this list of 1,000,000 transaction IDs. Use an algorithm that is O(n log n) or better — do not hand-roll a quadratic sort. Prefer the language’s built-in sort.”

Right-scope result:

def sort_transactions(ids: list[int]) -> list[int]:
return sorted(ids)

Python’s built-in sorted() uses Timsort, O(n log n) worst case, and is implemented in C — both algorithmically and constant-factor faster than any hand-rolled loop.

Lesson: judging AI output isn’t only “does it return the right value on my test case” — it’s “what’s its Big-O, and does that Big-O survive contact with the real n?” A correct-but-quadratic answer to a million-row problem is a bug that just hasn’t paged anyone yet.

🎮 Game Dev: decomposing enemy AI into sense → decide → act

Section titled “🎮 Game Dev: decomposing enemy AI into sense → decide → act”

Every enemy in a game — from the weakest goblin to the final boss — runs the exact same computational-thinking loop every single frame: sense the world → decide what to do → act on that decision. Cram all three into one function and you get code that’s impossible to test, impossible to extend, and impossible to reuse for the next enemy type. This is exactly why almost every studio’s real game AI is decomposed into the sense → decide → act pipeline you’re about to build.

Worked example — a tangled AI vs. a decomposed pipeline:

def enemy_update_naive(enemy: dict, player: dict) -> dict:
dx, dy = enemy["x"] - player["x"], enemy["y"] - player["y"]
dist = (dx * dx + dy * dy) ** 0.5
toward = 1 if enemy["x"] < player["x"] else -1
if dist < 5:
if enemy["hp"] < 20:
enemy["x"] -= toward # flee: move away from player
else:
enemy["x"] += toward # chase: move toward player
else:
enemy["x"] += enemy["patrol_dir"] # patrol
if enemy["x"] in (enemy["patrol_min"], enemy["patrol_max"]):
enemy["patrol_dir"] *= -1
return enemy

One function does everything at once: computing distance (sense), deciding whether to flee, chase, or patrol (decide), and moving the enemy (act). Add a new enemy type with different decision rules — say an Archer that shoots instead of walking up — and you have to edit this same if/else block over and over. And there’s no way to test “did it decide correctly” separately from “did it move correctly,” because the two are fused into one function.

Decompose it along the four pillars instead:

def sense(enemy: dict, player: dict) -> dict:
dx, dy = enemy["x"] - player["x"], enemy["y"] - player["y"]
return { # abstraction: keep only what decide() needs
"dist": (dx * dx + dy * dy) ** 0.5,
"low_hp": enemy["hp"] < 20,
"toward": 1 if enemy["x"] < player["x"] else -1,
}
def decide(percept: dict) -> str:
if percept["dist"] < 5:
return "flee" if percept["low_hp"] else "chase"
return "patrol"
def act(enemy: dict, action: str, percept: dict) -> dict:
if action == "flee":
enemy["x"] -= percept["toward"]
elif action == "chase":
enemy["x"] += percept["toward"]
else:
enemy["x"] += enemy["patrol_dir"]
if enemy["x"] in (enemy["patrol_min"], enemy["patrol_max"]):
enemy["patrol_dir"] *= -1
return enemy
def enemy_update(enemy: dict, player: dict) -> dict:
percept = sense(enemy, player) # 1. sense
action = decide(percept) # 2. decide
return act(enemy, action, percept) # 3. act

Watch all four pillars at once: decomposition splits one function into three, each owning exactly one job; abstraction shrinks percept down to dist, low_hp, and toward — the real hp, real x/y, sprite, and animation state are all dropped because decide() never needed them; pattern recognition means a new enemy type like an Archer or Ogre can reuse the exact same sense() and act(), changing only the rules inside decide(); algorithm design is the fixed order sense → decide → act that enemy_update() calls every single frame, never reshuffled.

SENSE read state DECIDE pick an action ACT change state next frame

Figure: the sense → decide → act pipeline, repeated every frame.

Now scale the problem up: with a Hero, Archer, Golem, and Ogre all sharing the same act phase (say, a turn-based battle), how do you know whose turn comes next? That’s abstraction again — strip everything about a unit away except one number, its speed — and now what’s left is a pure algorithm-design problem: whoever has the smallest time-to-act (next) goes first, then their clock gets pushed back by 100 / speed. That’s a priority queue wearing a game’s clothes. Click through below and watch who acts first.

Exercises

Game 1

A boss has 3 phases — aggressive → defensive → enrage — switching phase based on remaining HP% (100–66%, 65–33%, below 33%). Decompose this to fit the sense → decide → act pipeline above: what does sense() now need to report that it didn’t before, and how many layers of decision does decide() need?

Answer

sense() needs to add hp_percent to the percept, alongside the existing dist and low_hp. decide() now needs two layers: first pick the phase from hp_percent (aggressive/defensive/enrage), then pick the action within that phase from the other percepts (e.g. aggressive→chase, defensive→flee or block, enrage→a special attack). act() doesn’t change at all — it just executes whatever action was already decided. That’s the payoff of decomposition: the phase complexity lands entirely inside decide() and never touches sense()/act().

Game 2

A real enemy state dict has far more fields than this: x, y, hp, max_hp, sprite_id, animation_frame, loot_table, sound_on_hit, patrol_dir, patrol_min, patrol_max. Given the sense → decide → act pipeline above, which fields does the percept actually need to keep, and which can be discarded?

Answer

Needed to build the percept: only x, y (to compute dist and toward) and hp (to compute low_hp). Discardable from decide()’s point of view: max_hp, sprite_id, animation_frame, loot_table, sound_on_hit — all rendering or reward concerns, irrelevant to whether the enemy flees or chases. Meanwhile patrol_dir, patrol_min, patrol_max are needed by act() but not by decide() — notice abstraction isn’t one single cut for the whole pipeline; each function has its own “what actually matters.”

Game 3

A studio has three separate classes, GoblinAI, ArcherAI, OgreAI, each with its own update() method that recomputes distance from scratch and has an almost letter-for-letter if dist < 5: ... else: ... block, differing only in numbers and attack animations. What pattern is missing from this code, and how should it be fixed?

Answer

The missing pattern is a reusable sense → decide → act pipeline. All three classes copy-paste the distance calculation (sense) and the movement code (act) almost verbatim, when they should share one sense() and one act() between all three. Only decide()’s rules genuinely differ (e.g. pass a different decide function into each class, or override just decide()). The fix is to pull decide() out as the single point of variation, while sense()/act() are written once and shared — cutting duplication and cutting the number of places a bug can hide.

Game 4

From the <algo-turn-queue> widget above — Hero speed 12, Archer speed 16, Golem speed 6, Ogre speed 9 — each unit’s first time-to-act is 100/speed, and after acting that time is pushed back by another 100/speed. Trace it by hand: if you click “next turn” three times in a row, who acts 1st, 2nd, and 3rd?

Answer

Starting times-to-act (from 100/speed): Hero=8.33, Archer=6.25, Golem=16.67, Ogre=11.11.

Turn 1: smallest is Archer (6.25) → acts, rescheduled to 6.25+6.25=12.5. Turn 2: smallest is now Hero (8.33) → acts, rescheduled to 8.33+8.33=16.67. Turn 3: smallest is now Ogre (11.11) → acts, rescheduled to 11.11+11.11=22.22.

Order: Archer → Hero → Ogre. Notice Golem — the slowest — still hasn’t acted after 3 turns. That’s the priority queue always pulling out the smallest value: faster units naturally get more turns, with no special-case rule needed.

Challenge problems

Challenge 1 — decomposing the whole game loop

It’s not just enemy AI — the entire game loop (input → update → collision → render) decomposes the same way. Sketch what each stage should take in and return, and explain why an abstraction barrier between stages matters — for instance, why render should never mutate game state.

Approach

A simple sketch: actions = read_input() (input shouldn’t know anything about physics) → state = update(state, actions, dt) (bundles every enemy’s own sense→decide→act, returns new state, draws nothing) → state = resolve_collisions(state) (only sees positions/hitboxes, knows nothing about sprites) → render(state) (read-only, never allowed to modify state). Why the abstraction barrier matters: if render could mutate state, the game’s behavior would depend on when the screen happens to draw, making runs non-deterministic (rerun the same input and get a different result). It also becomes untestable, since you’d need an actual screen to observe any effect. Forcing each stage to “know only what it needs” is exactly what lets you unit-test update() without ever opening a game window.

Challenge 2 — a stealth game’s guard AI

Design a guard AI for a stealth game where sense() must read both noise level and light level around the player, not just distance — and where three guard types (a basic patrol guard, a high-alert guard, and a boss guard) all need to share the same sense()/act() but differ in decide(). Sketch the percept structure and say which pillar makes this design reusable across all three guard types.

Approach

A sketch percept: {"noise_level": float, "light_level": float, "player_visible": bool, "dist_to_last_seen": float} — keeping only the signals every decide() variant needs, never how many raycasts computed them or whether the noise came from footsteps or a breaking glass (all that sensor detail stays hidden inside sense()). The pillar that makes this reusable across three guard types is pattern recognition (all three share the identical “sense→decide→act” shape) working together with abstraction (the percept hides sensor implementation details from decide(), so even a boss guard with sharper sensors can still hand decide() the exact same percept shape). That leaves each guard type’s decide() differing only in thresholds/rules, with zero new sense()/act() code required.

  • Jeannette M. Wing, “Computational Thinking” (2006) — the short, seminal article that popularized the term. Communications of the ACM, Vol. 49, No. 3.
  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms (4th ed.), Ch. 1 — the formal definition of “algorithm” this lesson’s recipe framing is built on; read this chapter for the rigorous version of “algorithm as a well-defined procedure.”
  • Skiena, The Algorithm Design Manual (3rd ed.) — the best “which technique when” intuition once you’re past first principles, full of war stories about naive algorithms melting down at scale.
  • Bhargava, Grokking Algorithms (2nd ed.) — illustrated, beginner-friendly walkthroughs of exactly the sorts (bubble, selection, insertion) and Big-O ideas used in this lesson — a great visual companion to the widgets above.
  • Harvard CS50 — Week 0 — the opening lecture that explains computational thinking and algorithms using the phone-book example (cs50.harvard.edu).
  • MIT 6.0001 — Introduction to Computer Science and Programming in Python — the first week covers computational thinking before any programming (MIT OpenCourseWare).
  • Stanford CS106A — Programming Methodology — emphasizes decomposition and algorithm design as the foundation before writing code (Stanford Online).