We closed the previous lesson with an observation: history has left us not one programming language but hundreds, each with the obsessions of the era and the field in which it was born. For someone starting out, that abundance is more of a problem than an advantage. Where do you get in? Does it matter which one you pick first? Is any one of them "the best"?
This lesson brings order. You are going to learn the four axes by which any language is classified — level of abstraction, form of execution, type system and paradigm — you will see a practical comparison of the most widely used languages today, and you will come away with real criteria for choosing one for a specific project. We will finish by justifying why this course uses Python, which is neither an obvious choice nor the only possible one.
A useful warning right from the start: there is no best language, there is the right language for a context. Anyone who claims otherwise is selling something, usually their own experience dressed up as a universal law.
Contents
- Levels of abstraction: machine, assembly and high level
- Forms of execution: compiled, interpreted and bytecode
- Type systems: static vs dynamic, strong vs weak
- Programming paradigms
- Comparison of current languages
- How to choose a language: practical criteria
- Why this course uses Python
- Common mistakes and tips
- Exercises
- Conclusion
- Levels of abstraction: machine, assembly and high level
The first axis measures how much machine you have to keep in your head in order to write a line of code.
| Level | What you write | Portable | Example |
|---|---|---|---|
| Machine language | Binary the processor runs directly | No: tied to one processor | 10110000 01100001 |
| Assembly | Short names for processor instructions | No: tied to one family | MOV AL, 61h |
| High level | Ideas close to the problem | Yes, generally | print("Hello") |
Let us compare the same operation — adding two numbers and showing the result — at the two extremes. In assembly (x86, simplified):
mov eax, 12 ; load the value 12 into register EAX
add eax, 30 ; add 30 to EAX; EAX is now 42
mov [total], eax ; store EAX at the memory location called 'total'
; ...and ~15 more lines are still needed to convert the number to text
; and call the operating system to write it on screenIn Python:
The differences that matter:
- Registers and memory. In assembly you decide which register each value goes into. In Python you do not even know registers exist.
- Volume of code. What is two lines at high level is twenty in assembly.
- Portability. The Python code works on Luis's Windows and on Nuria's macOS without touching anything. The assembly above will not even run on a phone.
There is a nuance worth being clear about: low level is not "worse". Assembly and C are still used wherever every cycle must be squeezed or the hardware controlled directly: operating system kernels, device drivers, microcontrollers, demanding video games. It is simply an expensive tool: you pay in development time and in probability of error.
Within "high level" there are degrees. C is high level compared with assembly, but very close to the machine compared with Python. It is more useful to think of a gradient than of boxes:
flowchart LR
A["Machine language"] --> B["Assembly"] --> C["C"] --> D["Java / C#"] --> E["Python / JavaScript"] --> F["SQL"]
A -.- G["More control<br/>over the machine"]
F -.- H["Closer to<br/>the problem"]
- Forms of execution: compiled, interpreted and bytecode
In the first lesson we saw the difference between compiling and interpreting. Now we complete the picture with the in-between case, which is the one used by Java, C# and — partly — Python itself.
Ahead-of-time compilation. The compiler translates all the source code into machine code for the target system and produces an executable. This is what C, C++, Rust and Go do.
Interpretation. An interpreter reads the source code and runs it as it goes. There is no executable: to launch the program you need the interpreter installed.
Bytecode and virtual machine. An intermediate step: the code is compiled into a compact format called bytecode, which is not the machine code of any real processor but of a virtual machine. That virtual machine, which does exist for each system, runs the bytecode. This is how Java pulls off its trick: you compile once and the result works on any system that has the virtual machine.
flowchart TD
A["Source code"] --> B["Ahead-of-time compilation<br/>C, C++, Rust, Go"]
A --> C["Compilation to bytecode<br/>Java, C#"]
A --> D["Interpretation<br/>Python, JavaScript, PHP"]
B --> B1["Native executable<br/>(.exe, binary)"] --> Z["Processor"]
C --> C1["Bytecode<br/>(.class)"] --> C2["Virtual machine<br/>(JVM)"] --> Z
D --> D1["Interpreter reads and runs"] --> Z
| Aspect | Compiled | Bytecode + VM | Interpreted |
|---|---|---|---|
| What gets distributed | Native executable | Bytecode + requires a VM | Source code + requires an interpreter |
| Portability | Low: one per system | High | High |
| Typical speed | Very high | High | Medium |
| Write-test cycle | Slow (recompile) | Medium | Fast |
| Syntax errors | Detected before running | Detected before running | On reaching the line |
One detail about Python that often causes confusion: Python also compiles to bytecode, automatically and transparently (those are the .pyc files that appear in a __pycache__ folder). The difference from Java is that in Python it happens at run time, with no explicit step, which is why for practical purposes it behaves like an interpreted language. The boundaries are blurrier than the labels suggest.
- Type systems: static vs dynamic, strong vs weak
The type of a piece of data is the kind of value it is: an integer, a piece of text, a truth value. Languages differ in when they check types and in how seriously they take that check. These are two independent axes that are constantly confused.
Static vs dynamic: when the check happens
In a statically typed language, each variable has a type that is declared or inferred at compile time and does not change. Type errors are detected before running.
// Java: static typing
int taskCount = 5;
taskCount = "many"; // ERROR at compile time: it does not compileIn a dynamically typed language, the type is attached to the value, not to the variable, and is checked during execution.
# Python: dynamic typing
task_count = 5
task_count = "many" # Perfectly valid: now it is a piece of textIn Python the second line gives no error at all: the variable simply starts referring to something else. That convenience has a price: a type error is not discovered until the affected line runs, which may be in production, on a Tuesday, with a client watching.
Strong vs weak: how much the type is respected
This axis measures whether the language converts types on its own when the operation does not fit.
# Python: strong typing
print("Tasks: " + 5)
# TypeError: can only concatenate str (not "int") to strPython refuses: adding a piece of text and a number means nothing, so it raises an error and makes you decide. The correct form would be print("Tasks: " + str(5)).
// JavaScript: weak typing
console.log("Tasks: " + 5); // "Tasks: 5" -> converts the number to text
console.log("5" - 2); // 3 -> now converts the text to a number
console.log("5" + 2); // "52" -> and here it converts the other way againJavaScript always finds a way to make the operation mean something. It is convenient until it produces absurd results that nobody notices.
The four quadrants
| Strong typing | Weak typing | |
|---|---|---|
| Static | Java, C#, Rust, Go | C |
| Dynamic | Python, Ruby | JavaScript, PHP |
And here is the summary of pros and cons, which is what actually gets used when choosing:
| Static typing | Dynamic typing | |
|---|---|---|
| Errors detected | Before running | When running |
| Verbosity | Higher: types must be declared | Lower |
| Writing speed | Lower | Higher |
| Editor assistance | Excellent (it knows the types) | Limited |
| Best suited to | Large systems and big teams | Prototypes, scripts, learning |
A note for the future: for years now Python has allowed optional type annotations (count: int = 5). It does not check them at run time, but they help the editor and analysis tools. It is an example of how languages borrow good ideas from one another; we will not use them in this course, but it is worth knowing they exist.
- Programming paradigms
A paradigm is a way of organising your thinking about the program: what counts as the basic piece and how the pieces are combined. Let us look at the four main ones with a minimal fragment of each. They all solve the same task: getting Alba Studio's high-priority tasks.
Imperative and structured
You describe step by step how to reach the result, with sequences, conditions and loops. It is the paradigm everyone starts with and the one that dominates modules 2 to 5 of this course.
# Imperative: we describe the procedure
tasks = ["Sole logo (high)", "Fair poster (low)", "Nuria website (high)"]
urgent = []
for task in tasks: # go through them one by one
if "(high)" in task: # if it meets the condition...
urgent.append(task) # ...add it to the result listObject oriented
The basic piece is the object: a package containing data and the operations that act on it. The world is modelled as entities that collaborate.
# Object oriented: the task knows things about itself
class Task:
def __init__(self, title, assignee, priority):
self.title = title
self.assignee = assignee
self.priority = priority
def is_urgent(self):
return self.priority == "high"
t = Task("Sole logo", "Luis", "high")
print(t.is_urgent()) # TrueWe will study it thoroughly in module 7. For now it is enough to hold on to the idea: data and its behaviour travel together.
Functional
The basic piece is the function, understood in the mathematical sense: it receives data and returns a result, without modifying anything external. Changing state is avoided and composing functions is favoured.
# Functional: we describe the transformation, not the procedure
tasks = ["Sole logo (high)", "Fair poster (low)", "Nuria website (high)"]
urgent = list(filter(lambda t: "(high)" in t, tasks))There is no loop and no list being gradually filled: you state what you want ("the ones that meet this condition") and the language's machinery takes care of the how. We will see it in Functions as values.
Declarative: the case of SQL
You describe what you want to obtain, without saying anything at all about how to obtain it. The best-known case is SQL, the language of databases.
Nowhere does it say how to go through the data or in what order: the database engine decides that. It is abstraction taken to its extreme, and it works very well in bounded domains.
| Paradigm | Basic piece | Question it answers | Example language |
|---|---|---|---|
| Imperative/structured | The instruction | What steps must be taken? | C, Python |
| Object oriented | The object | What entities are there and what do they do? | Java, C++, Python |
| Functional | The function | What transformation do I apply? | Haskell, Scala, Python |
| Declarative | The description of the result | What do I want to obtain? | SQL, HTML |
Notice that Python appears in three rows. Most modern languages are multi-paradigm: they do not force you to choose, and in one and the same program an imperative loop, a class and a lambda function live side by side. That is another reason why Python works well as a first language: it lets you cover the three styles without changing tools.
- Comparison of current languages
| Language | Typing | Execution | Typical uses | Learning curve |
|---|---|---|---|---|
| Python | Dynamic, strong | Interpreted (internal bytecode) | Data science, AI, automation, web backend, scripting | Gentle |
| JavaScript | Dynamic, weak | Interpreted (JIT in the browser) | Web frontend, backend with Node.js, hybrid mobile apps | Medium (the language is easy; the ecosystem is not) |
| Java | Static, strong | Bytecode + virtual machine | Enterprise software, Android, large-scale systems | Medium-high (a lot of initial ceremony) |
| C | Static, weak | Compiled to native | Operating systems, drivers, embedded systems | High (manual memory, pointers) |
| C# | Static, strong | Bytecode + CLR | Windows applications, video games with Unity, enterprise backend | Medium |
| SQL | — (declarative) | Interpreted by the engine | Querying and manipulating databases | Gentle for the basics, high for the advanced |
| Go | Static, strong | Compiled to native | Cloud services, network tools, containers | Medium-gentle (deliberately small language) |
| Rust | Static, strong | Compiled to native | Critical systems, performance with memory safety | Very high |
| PHP | Dynamic, weak | Interpreted | Server-side websites, WordPress, e-commerce | Gentle |
A few readings worth taking from this table:
- The learning curve does not measure power. Rust is extremely hard because it forces you to prove to the compiler that your memory management is correct. That does not make it better for writing a task manager for three people.
- Languages overlap. Python, Java, C# and Go can all write the backend of a web application. The choice depends more on context than on capabilities.
- SQL does not compete with the rest. It is a specialised language used from the others: a Python program that queries a database has SQL inside it. You will know Python and you will know SQL, not one or the other.
- How to choose a language: practical criteria
When someone asks "which language should I use?" without saying what for, the question has no answer. These are the four criteria that do give one.
1. The problem domain. By far the most decisive criterion. There are fields where the decision is practically made in advance:
| If the problem is... | The natural choice is... | Because... |
|---|---|---|
| Interactivity on a web page | JavaScript | It is the only one browsers run |
| Data analysis or machine learning | Python | That is where pandas, NumPy, scikit-learn and PyTorch are |
| Native iPhone app | Swift | It is Apple's official ecosystem |
| Native Android app | Kotlin | It is Google's official ecosystem |
| Kernel of an operating system | C or Rust | They need direct control of memory and hardware |
| Querying a database | SQL | It is the engine's language |
2. The ecosystem. A language without libraries is a language without practical use. Before choosing, ask yourself whether there are mature libraries for what you need, plentiful documentation, answers to the typical problems and people maintaining it. An elegant language with a poor ecosystem will force you to rewrite things that elsewhere are one line.
3. The team. The best language for a project is usually the one already known by the people who will maintain it. Introducing a new language costs months of productivity and creates a dangerous dependency: if the only person who masters it leaves, the project is orphaned. This unglamorous criterion decides a great many real projects.
4. Performance and technical constraints. It matters less than people think, but when it matters it is non-negotiable. If you have to process millions of operations per second, or run on a microcontroller with 16 KB of memory, or guarantee a maximum response time, the list of candidates shrinks by itself.
flowchart TD
A["I have a problem"] --> B{"Does the domain impose<br/>a language?"}
B -->|Yes| C["Use it:<br/>browser, iOS, database..."]
B -->|No| D{"Are there hard performance<br/>or hardware constraints?"}
D -->|Yes| E["Compiled language:<br/>C, C++, Rust, Go"]
D -->|No| F{"Does the team already master<br/>a suitable language?"}
F -->|Yes| G["Use that one"]
F -->|No| H["Choose by ecosystem<br/>and ease of learning"]
A tip that saves arguments: the second language costs far less than the first. The concepts in this course — variables, conditionals, loops, functions, data structures, objects — are the same in almost all of them. What changes is the syntax, which is the cheap part. Learning one properly is worth more than nibbling at five.
- Why this course uses Python
With all of the above on the table, choosing Python to learn programming stops being a fashion and becomes a defensible decision:
- Minimal syntax.
print("Hello")is a complete program: there is no class to declare, no main method, nothing to import. Compared with the six lines Java demands for the same thing, the learner spends their attention on the concept rather than on the ceremony. - A very short write-test cycle. Being interpreted, there are two seconds between writing a line and seeing its result. Learning is trying many times, and every second of waiting discourages trying.
- Readability by design. Compulsory indentation forces tidy code from day one. A well-written Python program reads almost like pseudocode, which helps a lot while you are still translating between idea and code.
- Multi-paradigm. It lets you cover the imperative style (modules 2-3), the functional style (module 4) and object orientation (module 7) without changing language or tools.
- Immediate real usefulness. It is not a "toy" classroom language. It is one of the most sought-after tools on the market in automation, data and AI. What you learn here works outside.
- Enormous ecosystem and community. Almost any question you have, someone has already had, and it is answered in writing.
It is also honest to say what is not the best thing about Python: it is not the fastest language at run time, its dynamic typing lets errors reach production that a static language would catch earlier, and managing dependencies across projects requires discipline (which is why the virtual environments we will see in the next lesson exist). For EasyTask — a small program, for three people, which needs to be up and running soon — none of those drawbacks is relevant. That reasoning, and not the list of virtues, is the correct way to choose a language.
Common Mistakes and Tips
Looking for "the best language". There is no such thing. There is the best language for this problem, this team and this deadline. Arguments about absolute superiority consume a lot of time and produce nothing.
Confusing dynamic typing with "no types". Python has types, and very strict ones: that is why "Tasks: " + 5 fails. What is dynamic is when they are checked, not whether they exist.
Believing that interpreted simply means slow. For 95% of programs, the speed of the language is irrelevant next to network time, disk time or the user's own time. Optimising the language before knowing where the bottleneck is, is wasted effort; we will see this in Efficiency and Big-O notation.
Hopping between languages every few weeks. It is the costliest mistake beginners make: you end up with five introductions and not one finished program. Finish this course with Python; the second language will cost you a fraction of the effort.
Thinking that learning Python is learning to program. What you are learning are concepts — breaking problems down, controlling flow, structuring data — that outlive the language. Python is the vehicle, not the destination.
A tip: when you read code in a language you do not know, do not try to understand every symbol. First look for the three structures of structured programming — sequence, condition, repetition — and the functions. With those you will recognise the logic of almost any code, even if the syntax feels alien.
Exercises
Exercise 1: Classifying languages
For each language, state: (a) whether its typing is static or dynamic, (b) whether it is strong or weak, (c) its usual form of execution, and (d) a typical use.
- Python
- Java
- JavaScript
- C
Exercise 2: Choosing a language with judgement
For each scenario, propose a language and justify the choice with at least two of the four criteria from section 6 (domain, ecosystem, team, performance):
- Alba Studio wants the website it shows clients to have a form that validates the data without reloading the page.
- A company needs the firmware for a sensor with 32 KB of memory that must respond in under a millisecond.
- Marta wants a monthly report that reads a sales file and works out averages and charts. Nobody at the studio programs, but Nuria once took a Python course.
- A bank must maintain a 400,000-line system written in Java fifteen years ago, with a team of twelve people who know it.
Exercise 3: Identifying the paradigm
State which paradigm each fragment corresponds to and explain what you looked at:
# D
class TaskManager:
def count_pending(self):
return len([t for t in self.tasks if t.status == "pending"])Solutions
Solution 1.
| Language | Typing (when) | Typing (how much) | Execution | Typical use |
|---|---|---|---|---|
| Python | Dynamic | Strong | Interpreted (with internal bytecode) | Data, AI, automation |
| Java | Static | Strong | Bytecode + virtual machine | Enterprise software, Android |
| JavaScript | Dynamic | Weak | Interpreted in the browser | Web frontend |
| C | Static | Weak | Compiled to native | Operating systems, embedded |
The case of C surprises many people: it is static but weak, because it allows implicit conversions between numeric types and pointers that can produce meaningless results without the compiler complaining.
Solution 2.
- JavaScript. Domain: it is the only language browsers run, so validating without reloading the page imposes it. Ecosystem: there are mature, ready-to-use form validation libraries.
- C, or Rust if the team masters it. Performance and constraints: 32 KB does not fit a Python interpreter or a virtual machine, and a guaranteed response time demands control of the hardware. Domain: embedded systems are C's historical territory, with compilers for practically any microcontroller.
- Python. Team: Nuria already has a foundation, and in a three-person studio that weighs far more than any technical advantage. Ecosystem: pandas and matplotlib handle data reading, calculations and charts with almost no code of your own.
- Java. Team: twelve people know the system; rewriting it in another language would destroy fifteen years of accumulated knowledge and already-solved edge cases. Ecosystem: the environment around those 400,000 lines (libraries, servers, tools) is already set up and proven. The answer "let us rewrite it in something modern" is almost always the wrong one.
Solution 3.
- A: imperative/structured. The procedure is described step by step: an accumulator variable, a loop that goes through elements and a condition. It says how to count.
- B: declarative. The desired result is described without indicating any traversal or order. The database engine decides how to obtain it.
- C: functional. A transformation (
filter) is applied with an anonymous function (lambda) and the result is composed, with no variables being modified and no explicit loop. - D: object oriented. The code lives inside a class and operates on the object's own data (
self.tasks); data and behaviour are packaged together. Notice that inside it uses a functional-style construct: paradigms mix perfectly naturally.
Conclusion
Programming languages are classified by four axes you now know how to read: their level of abstraction (from the machine to the problem), their form of execution (compiled, bytecode on a virtual machine, or interpreted), their type system (static or dynamic, strong or weak) and their paradigm (imperative, object oriented, functional or declarative). No axis establishes superiority: it establishes trade-offs. And choosing is a matter of four criteria — domain, ecosystem, team and performance — applied to a specific context, not of personal preference.
With those criteria we have justified Python for this course and for EasyTask: minimal syntax, a short test cycle, readability forced by design, multi-paradigm, and with real professional usefulness beyond the classroom. Its drawbacks exist, but none of them affects a small program for a three-person studio.
We now know what programming is, where the trade comes from and what we are going to do it with. What remains is the most down-to-earth part: setting up the workstation. In the next lesson, Development environments, we will install Python on Windows, macOS or Linux, check that it works, learn the difference between the interactive interpreter and running a file, choose an editor, organise the project folders and finally create easytask.py, the file that will stay with us for the rest of the course.
Fundamentals of Programming
Module 1: Introduction to Programming
- What is programming?
- History of programming
- Programming languages
- Development environments
- From problem to algorithm
Module 2: Core Concepts
- Variables and data types
- Operators and expressions
- Input and output
- Type conversion and data validation
Module 3: Control Structures
Module 4: Functions and Procedures
- Defining and using functions
- Parameters and return values
- Variable scope
- Breaking a program down into functions
- Functions as values: lambda and higher order
Module 5: Data Structures
- Lists and arrays
- Strings
- Dictionaries and sets
- Tuples and nested structures
- Saving data to files: text, CSV and JSON
Module 6: Basic Algorithms
Module 7: Objects and Code Organisation
- From data to objects: classes and instances
- Attributes, methods and the constructor
- Collections of objects
- Modules, packages and imports
Module 8: Good Practices and Tools
- Documentation and comments
- Debugging and error handling
- Version control
- Automated testing
- Style, readability and refactoring
