If you've started learning programming, chances are the first thing you did was open an editor and write some code.

Maybe something like:

print("Hello, world!")

And that's where the adventure begins.

But there's a problem.

Programming doesn't start with code.

Code is simply the final form in which we explain a solution to a computer — a solution we first have to figure out ourselves.

Before we learn Python, FastAPI, or any other programming language, there is something much more important we should learn:

how to break a problem down.


Analytical and Synthetic Thinking

In psychology, among other concepts, we find analytical thinking and synthetic thinking.

In simple terms, analytical thinking tries to understand a whole by breaking it down into its individual parts.

Synthetic thinking goes in the opposite direction: it takes those elements and the relationships between them and brings them together into a coherent whole.

And this is where things get interesting when we look at programming.

A programmer does both.

We analyze the problem.

We break it down.

Then we synthesize the solution.

We build it from smaller components.

There's a good expression for the first part:

To split hairs.

Usually, we use it when talking about someone who is looking too closely at details.

In programming, however, if you do it at the right time, it's a superpower.


Let's Take a Car Apart

Let's start with something simple.

A car.

We're not interested in how an engine works or how a car is manufactured.

We simply want to represent a car in a program.

The first question is:

What do we know about a car?

We might say:

  • its brand;
  • its model;
  • its year;
  • its color;
  • its registration number;
  • its mileage;
  • its fuel level;
  • its speed;
  • its position.

These are data about the object.

But a car isn't just a collection of data.

What can it do?

It can:

  • start;
  • stop;
  • accelerate;
  • brake;
  • refuel;
  • drive.

And here we encounter a fundamental idea:

An object has both state and behavior.

State describes what the object is like at a given moment.

Behavior describes what the object can do.


But What Is Its Main Behavior?

We could put starting, braking, acceleration and refueling into a list.

But if we ask:

What was a car primarily created for?

The answer is obvious:

to get you from one place to another.

In our software model, we could represent that with a behavior such as:

drive()

And we've just taken an important step without writing much code at all.

We've started identifying the responsibilities of the object.


But We're Not Copying Reality

This is where we need to be careful.

A real car has thousands of characteristics.

It has an engine, transmission, suspension, brakes, sensors, oil, temperature, tire pressure, electrical components, and much more.

Do we need to put all of that into our class?

No.

And this is where abstraction comes in.

We aren't trying to copy reality into our program.

We're building a model of reality that contains only the things relevant to the problem we're trying to solve.

If we're building a vehicle management application, we might only need:

 
brand
model
registration
mileage
 

If we're building a game, we might need:

 
position
speed
fuel
 

If we're building a service application, we might care about:

 
mileage
last_service
oil_level
 

The same real-world car can become completely different software models.

It depends on the problem we're trying to solve.


And This Is Where the Class Comes In

Once we've analyzed the object, we could represent it in Python with a class:

class Car:
    ...

But notice the order.

We didn't start with the class.

First we asked:

What is the object?

What state does it have?

What can it do?

What responsibilities does it have?

Which parts of reality are relevant to us?

Only after we've built the model in our minds do we have a reason to express it in code.

A class becomes, in a sense, the model from which we can create concrete objects.

For example:

class Car:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def drive(self):
        print("The car is driving")

And then we can create a concrete object:

my_car = Car("Dacia", "Logan")

Car is the model.

my_car is a concrete object created from that model.

But the important part isn't the syntax.

The important part is the thinking process that led us there.


Now Let's Make Things More Interesting

So far, we've analyzed an object.

Something concrete that we can see and touch.

But programs don't only work with physical objects.

Sometimes we need to model processes.

Let's take something simple:

You want to order a pair of shoes online.

At first glance, it sounds like a single action:

"I order the shoes."

But what happens when we start splitting it apart?

 
choose the product
    ↓
choose the size
    ↓
add it to the cart
    ↓
check inventory
    ↓
calculate the price
    ↓
choose a payment method
    ↓
process the payment
    ↓
create the order
    ↓
update inventory
    ↓
send confirmation
 

One simple action has turned into a whole sequence of steps.

And if we look closer, we can also identify some entities:

 
Customer
Product
Cart
Order
Payment
Inventory
Notification
 

Now we're no longer taking apart a car.

We're taking apart a system.

And we start asking questions:

What does each of these entities know?

What can each of them do?

What relationships exist between them?

Who is responsible for each operation?

And without even realizing it, we've started designing an application.


From Problem to Code

This is one of the paths I want us to follow on this blog:

 
PROBLEM
   ↓
ANALYSIS
   ↓
DECOMPOSITION
   ↓
ENTITIES
   ↓
DATA + BEHAVIOR
   ↓
RELATIONSHIPS
   ↓
ABSTRACTION
   ↓
MODEL
   ↓
CODE
 

Notice something important.

Code is the last step.

Not the first.

Programming isn't about memorizing hundreds of commands.

It's about being able to take something complicated, understand it, break it down, and build a solution that can eventually be expressed in a programming language.

And if you've done the thinking part well, the language becomes the tool.

Not the other way around.


You Don't Have to Think Like Python

This is probably one of the most important ideas I want to convey here.

You don't need to learn how to think in Python.

You need to learn how to think algorithmically.

Today you might express your solution in Python.

Tomorrow you might write it in Go.

The day after that, Rust.

Maybe one day, C.

The problem and the conceptual solution can remain the same.

Only the language we choose to express them changes.

That's why I want to start this series before Python.

Because Python is what we'll use to write our code.

But thinking is what we're actually trying to learn.


Before We Write Code

Before we write our first serious program, we need to learn how to look at a problem through a programmer's eyes.

Break it down.

Figure out what matters and what doesn't.

Identify the objects, data, behaviors and relationships between them.

Then rebuild everything into a form that a computer can execute.

Only then does the code begin.