Imagine you have just learned a new language. You know the words, understand basic grammar, and can construct simple sentences. But does that mean you can write a novel?
Not quite.
Writing a novel requires much more than knowing words and grammar. You need to organize your ideas into paragraphs, chapters, and stories. You need to avoid repeating yourself, keep everything consistent, and make sure your readers can follow what you are trying to say.
Programming is surprisingly similar.
In our previous chapter, Same Thought, Different Words: Why Every Programming Language Speaks the Same Logic, we learned the basic building blocks of programming languages: variables, inputs and outputs, conditions, loops, and other fundamental commands.
With these building blocks, we can already write programs that solve problems. But imagine building a large application, such as an online banking system, a computer game, or a social media platform. Such applications might contain hundreds of thousands, or even millions, of lines of code.
Can we simply write all those lines, one after another, in a single enormous program? Technically, we could write a very large program that way. But developing it, understanding it, finding mistakes, and making changes would become extremely difficult. And what if hundreds of programmers need to work on the same application?
Fortunately, programming languages provide tools that help us organize code, avoid unnecessary repetition, reuse existing solutions, and handle unexpected situations. In this chapter, we will explore some of the most important of these tools:
- Functions
- Error handling
- Organizing code into files and packages
- Libraries
- Open-source communities
- Object-oriented programming
We will introduce object-oriented programming in this chapter, and the next chapter will be fully dedicated to it.
These tools do two things at once. They help us code faster, because we stop writing the same thing again and again. And they help us code with higher quality, because organized code is easier to read, test, and fix.
This is the difference between someone who knows the words and someone who has become a craftsman.
As usual, we will see each idea in four languages: Python, C, JavaScript, and Java. And when we reach object-oriented programming, we will also meet C++, the language that brought objects to C.
Comments
Before we dive into the examples, there is one more thing to learn: comments.
Comments, as the name suggests, are notes written inside the code, usually by the developer who wrote it. They are not part of the program's logic: the compiler (or interpreter) ignores them when translating the code into binary.
Comments are used to explain parts of the code to other developers. As I usually say, if I try to read my own code six months after writing it, I will struggle to remember why I wrote a particular line. And when a different developer tries to understand your code, it is even harder.
A good practice for clean programming is to add a comment above every function you write, explaining what it does, and to add comments to any complicated piece of code.
Many languages have two ways to mark text as a comment: one for a single-line comment, and one for a multi-line comment (a block of text). C, Java, and JavaScript use // for a single-line comment and /* */ for a multi-line comment. Python uses # for a single line and """ """ (triple quotes) for multiple lines.
Let's code with comments:
Python
# This is a single-line comment in Python
print("Hello, world!")
"""
This is a multi-line comment
in Python
"""
print("Comments are ignored")
JavaScript
// This is a single-line comment in JavaScript
console.log("Hello, world!");
/*
This is a multi-line comment
in JavaScript
*/
console.log("Comments are ignored");
Java
// This is a single-line comment in Java
System.out.println("Hello, world!");
/*
This is a multi-line comment
in Java
*/
System.out.println("Comments are ignored");
C
// This is a single-line comment in C
printf("Hello World!\n");
/*
This is a multi-line comment
in C
*/
printf("Comments are ignored");
I will use comments in the code examples of this chapter and the following chapters to explain different parts of the code.
Functions: give your logic a name
A function is a named piece of code that does one job. You write it once, and then you can "call" it by its name as many times as you like.
Imagine you are preparing a cookbook. On page 12, you wrote a recipe for tomato sauce. Several other recipes require the same sauce: pasta, pizza, and lasagna. Would you rewrite the entire tomato sauce recipe every time? Of course not. You would write the recipe once, and every other recipe would simply say "see page 12."
A function is page 12.
A function has three parts, and you already know them from the algorithms chapter:
- Input (called parameters): the values we hand to the function.
- Process: the steps inside the function.
- Output (called the return value): the result the function hands back.
A simple example
Let's imagine we are writing an application for an online shop. Whenever a customer purchases a product, we want to calculate its price after adding a 10% tax. The calculation is straightforward:
Final Price = Price × 1.10
We could repeat this calculation everywhere in our application. But what happens when the tax rate changes? We would need to find every place where we wrote the calculation and update it.
Instead, let's create a function called calculateTotal. The function will receive the original price as a parameter, calculate the final amount, and return the result.
How a function works
- Input: Price = 100
- Function: calculateTotal
- Multiply price by 1.10
- Output: 110
Let's code it in four languages. In all the examples, we define a function called calculateTotal(), and then use an output command to call it, get the result, and print it. Notice that the print command itself is a function: it receives text as a parameter and prints it.
Python
# Python uses the word def to define a function
def calculateTotal(price):
return price * 1.10
print(calculateTotal(100))
C
/*
In C, you must define the type of the return value and the type of each
expected parameter. In this case, both are double (a decimal number).
*/
double calculateTotal(double price) {
return price * 1.10;
}
int main(void) {
printf("%.2f\n", calculateTotal(100));
return 0;
}
JavaScript
// JavaScript uses the word function to define a new function
function calculateTotal(price) {
return price * 1.10;
}
console.log(calculateTotal(100));
Java
// Java uses a syntax similar to C: the return type comes first
double calculateTotal(double price) {
return price * 1.10;
}
void main(String[] args) {
System.out.println(calculateTotal(100));
}
Although the syntax differs, the principle is identical. Each function has a name, may receive information called parameters, performs a task, and can return a result to the program that called it.
Not all functions need parameters, and not all functions return values. For example, a function might simply display a message on the screen, like the print() function.
Python uses def to declare a function, JavaScript uses the word function, while C and Java write the type of the result first (double, String). But the idea is identical in all four.
Let's code another example. Let's take the triangle example from the algorithms chapter. Instead of writing the classification logic every time we need it, we wrap it in a function called classify_triangle.
Python
"""
Function to classify triangle
"""
def classify_triangle(a, b, c):
if a == b == c:
return "Equilateral"
elif a == b or b == c or a == c:
return "Isosceles"
else:
return "Scalene"
# call the function classify_triangle with parameters: 3,3,3
triangle_type = classify_triangle(3, 3, 3)
print(triangle_type)
# call the function again, this time with parameters 3,4,5
triangle_type = classify_triangle(3, 4, 5)
print(triangle_type)
C
/*
Function to classify triangle
*/
const char* classify_triangle(int a, int b, int c) {
// In C, a string (text) is written as char*. char is short for character,
// since C treats text as a list of characters. The * means it is a pointer
// to a place in memory where the value is stored
if (a == b && b == c)
return "Equilateral";
if (a == b || b == c || a == c)
return "Isosceles";
return "Scalene";
}
int main() {
const char* triangle_type = classify_triangle(3, 3, 3);
printf("%s\n", triangle_type);
// Notice we can make the code shorter by calling the function
// directly as the parameter of the print command
printf("%s\n", classify_triangle(3, 4, 5));
return 0;
}
JavaScript
/*
Function to classify triangle
*/
function classifyTriangle(a, b, c) {
if (a === b && b === c)
return "Equilateral";
if (a === b || b === c || a === c)
return "Isosceles";
return "Scalene";
}
// Assign the return value of the function classifyTriangle()
// to a variable named triangle_type
let triangle_type = classifyTriangle(3, 3, 3);
console.log(triangle_type);
console.log(classifyTriangle(3, 4, 5));
Java
/*
Function to classify triangle
*/
String classifyTriangle(int a, int b, int c) {
if (a == b && b == c)
return "Equilateral";
if (a == b || b == c || a == c) return "Isosceles";
return "Scalene";
}
static void main(String[] args) {
String triangle_type = classifyTriangle(3, 3, 3);
System.out.println(triangle_type);
System.out.println(classifyTriangle(3, 4, 5));
}
Reserved words: every language has a list of words that are part of the language itself, and you cannot use them for purposes they were not intended for.
You may have noticed that the C and Java programs have a function called main() (Python has a similar idea, with a different syntax). main() is a special function name: there can be only one main function in the whole application, and its purpose is to be the application's starting point. In other words, when you run the application, the computer needs to know where in the code to start running your workflow, and that place is the main function.
Why functions matter
Functions provide several important benefits.
- Reusability: write the logic once and call it from many places.
- Readability: a function named
calculateTotaltells us what the code does without requiring us to understand every calculation inside it. - Maintainability: if the calculation changes, we update the function in one place.
- Testing: we can test an individual function to verify that it behaves correctly.
Functions also introduce an important programming principle: abstraction. Abstraction means we can use something without needing to understand every detail of how it works internally.
When you drive a car, you press the accelerator without calculating how much fuel the engine should inject into its cylinders. Likewise, a programmer can call a function without needing to examine all the instructions inside it.
Error handling: what happens when things go wrong?
Imagine using an ATM to withdraw money. You enter an amount, but the machine cannot connect to the bank. Should the ATM simply stop working or display a mysterious technical message? Of course not. It should explain that the transaction cannot be completed and allow you to try again later.
Computer programs operate in an imperfect world. Every program will eventually meet a bad day.
The user types letters where a number was expected.
A file is missing.
The internet connection drops in the middle of a payment.
Error handling is how we tell the computer what to do when things go wrong, instead of letting it crash.
Think about a pilot. Before every flight, the pilot does not only plan the normal route. There is a checklist for an engine failure, a plan for bad weather, and an alternative airport. Nobody hopes to use them, but a professional prepares for them.
Error handling is the pilot's emergency checklist.
Most modern languages use the same idea, called try / catch (in Python: try / except):
- Try: "try to do this."
- Catch: "if something goes wrong, do this instead."
- Finally (optional): "whatever happens, always do this at the end," like closing a file.
Let's return to our triangle example. A triangle cannot have a side of length zero or less. Our function should refuse such input clearly, and the caller should handle it gracefully.
Python
def classify_triangle(a, b, c):
if a <= 0 or b <= 0 or c <= 0:
# the function throws an exception (error) back to its caller
raise ValueError("Sides must be positive")
# ... classification logic ...
# try runs the code and watches for errors
try:
print(classify_triangle(3, 0, 5))
# check whether the function threw an exception (error)
except ValueError as error:
# if there is an error, handle it
print("Oops:", error)
JavaScript
function classifyTriangle(a, b, c) {
if (a <= 0 || b <= 0 || c <= 0) {
// the function throws an exception (error) back to its caller
throw new Error("Sides must be positive");
}
// ... classification logic ...
}
// check whether the function threw an exception (error)
try {
console.log(classifyTriangle(3, 0, 5));
} catch (error) {
// if we catch an error, handle it
console.log("Oops:", error.message);
}
Java
String classifyTriangle(int a, int b, int c) {
if (a <= 0 || b <= 0 || c <= 0) {
// the function throws an exception (error) back to its caller
throw new IllegalArgumentException("Sides must be positive");
}
// ... classification logic ...
}
try {
System.out.println(classifyTriangle(3, 0, 5));
} catch (IllegalArgumentException error) {
// if we catch an error, handle it
System.out.println("Oops: " + error.getMessage());
}
C is older than the try and catch idea, so it has no such command. In C, a function reports a problem through its return value, and the caller must check it. It works, but it relies on discipline: if a programmer forgets to check, the error passes silently. For example, this function returns 0 on success and -1 on error:
C
int classify_triangle(int a, int b, int c, const char** result) {
if (a <= 0 || b <= 0 || c <= 0)
return -1;
/* ... classification logic, sets *result ... */
// return 0 on success
return 0;
}
int main() {
const char* type;
// check the return value
if (classify_triangle(3, 0, 5, &type) != 0) {
printf("Oops: sides must be positive\n");
} else {
printf("%s\n", type);
}
return 0;
}
This difference tells a story. try / catch was invented exactly because the C way was easy to forget. It separates the "happy path" (the normal logic) from the "bad day" logic, and makes errors hard to ignore.
Why error handling gives higher quality: a program that crashes loses the user's work and their trust. A program that says "Please enter a positive number" and lets them try again feels professional. In critical systems like banking, healthcare, or aviation, good error handling is not a luxury. It is the difference between a small problem and a disaster.
Files, modules, and packages: organizing the story into chapters
Let's return to our book analogy. Imagine writing a 1,000-page novel without chapters, sections, or paragraphs. Even if the story were excellent, reading and editing it would be a nightmare.
Software has the same problem. A small program might fit comfortably into a single file. But as the application grows, keeping everything in one file becomes difficult.
Let's imagine we are developing an online shopping application. It needs to manage customers, products, payments, deliveries, and inventory. Instead of putting all the code together, we can organize it into separate files, each responsible for a specific part of the application.
The file names illustrate one possible Python project structure. Other languages use different conventions. (Files with the .py extension usually contain Python code.)
-
OnlineShop/
-
customers/
- customer.py
- address.py
-
products/
- product.py
- inventory.py
-
payments/
- payment.py
- invoice.py
-
shipping/
- delivery.py
-
customers/
Each file contains code related to a particular responsibility. This makes our application easier to understand, maintain, test, and develop with a team.
But separating code into files creates a new question. How can code in one file use a function defined in another? Programming languages provide mechanisms for sharing and importing code.
Let's see an example with our triangle function. If we break it into files:
-
geometry/// this is the package, the main folder
- triangles.py// file with triangle functions
- circles.py// file with circle functions
- main.py// the program that uses them
Let's code it:
Python
# This code is in the file geometry/triangles.py
def classify_triangle(a, b, c):
...
# This code is in the file main.py
# The word "from" says: look in the file geometry/triangles.py
# The word "import" says: bring the function classify_triangle
from geometry.triangles import classify_triangle
# Now we can use it in this file
print(classify_triangle(3, 4, 5))
JavaScript
/* This code is in the file geometry/triangles.js
Files with the .js extension are usually JavaScript files.
Notice that in operating systems like Linux, extensions are only a naming convention */
// JavaScript uses the word "export" to publish the functions other files may use
// We will go deeper into access control in the next chapter, on object-oriented programming
export function classifyTriangle(a, b, c) {
// ...
}
// This code is in the file main.js
// JavaScript uses {function name} to define which function to import
// and "from" to define the file where that function lives
import { classifyTriangle } from "./geometry/triangles.js";
console.log(classifyTriangle(3, 4, 5));
In Java, packages are built into the language. Every file declares which package it belongs to, and the folder structure must match it.
Java
// This code is in the file com/unboxing/geometry/Triangles.java
// Notice that every Java file uses the word package to declare its location
package com.unboxing.geometry;
public class Triangles {
public static String classifyTriangle(int a, int b, int c) { /* ... */ }
}
// This code is in the file Main.java
// Java uses the word "import" followed by the package and class name
import com.unboxing.geometry.Triangles;
public class Main {
public static void main(String[] args) {
System.out.println(Triangles.classifyTriangle(3, 4, 5));
}
}
C splits each module into two files: a header (.h), which announces what the module offers, and a source file (.c), which holds the actual code.
C
/* file: triangles.h - the "table of contents" */
const char* classify_triangle(int a, int b, int c);
/* file: triangles.c - the actual code */
// Notice that in C, # is not a comment: #include brings in the header file
#include "triangles.h"
const char* classify_triangle(int a, int b, int c) { /* ... */ }
/* file: main.c */
// the headers that declare the functions I need
#include <stdio.h>
#include "triangles.h"
int main() {
printf("%s\n", classify_triangle(3, 4, 5));
return 0;
}
Notice that some languages ask us to choose what to share. JavaScript uses export, Java uses public. Everything else stays private inside the file. This is important: each file shows the world a small, clean "front door" and hides its internal details.
Why files make us faster: in a team of 50 programmers, each person can work on a different file at the same time without stepping on each other's toes.
Why files give higher quality: small, focused files are easier to understand, test, and review. When something breaks in the triangle logic, everyone knows exactly where to look.
Libraries: don't reinvent the wheel
A library is a collection of ready-made functions, written and tested by someone else, that we can bring into our program. If files are the chapters of our own book, libraries are the other books on the shelf that we are allowed to quote.
Think about building a house. A builder does not make bricks, wires, and pipes from scratch. They buy standard parts that someone else already produced and tested, and they focus on the design of the house. Libraries are a programmer's standard parts.
Every language comes with a standard library: a large toolbox that ships with the language itself. It covers math, dates, text, files, networking, and much more.
Let's use it to calculate the area of our triangle with Heron's formula, which needs a square root. Nobody writes their own square root function. We take it from the math library.
Python
# Import a library called math
import math
def triangle_area(a, b, c):
s = (a + b + c) / 2
# use the sqrt function from the math library to calculate the square root
# you don't need to know how to calculate it yourself; someone already coded it for you
return math.sqrt(s * (s - a) * (s - b) * (s - c))
print(triangle_area(3, 4, 5)) # 6.0
C
/*
The stdio library contains functions for input and output,
for example the printf function we use to print
*/
#include <stdio.h>
// the math library contains mathematical functions
#include <math.h>
double triangle_area(double a, double b, double c) {
double s = (a + b + c) / 2;
// use the sqrt function from the math library
return sqrt(s * (s - a) * (s - b) * (s - c));
}
int main() {
printf("%.1f\n", triangle_area(3, 4, 5));
return 0;
}
JavaScript
function triangleArea(a, b, c) {
const s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
console.log(triangleArea(3, 4, 5)); // 6
Java
/*
class is a word that belongs to object-oriented programming. We touch on it
in this chapter and cover it fully in the next one
*/
public class Area {
static double triangleArea(double a, double b, double c) {
double s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
public static void main(String[] args) {
System.out.println(triangleArea(3, 4, 5)); // 6.0
}
}
Look carefully, and you will notice something: you have been using libraries since the first chapter. printf in C comes from the stdio library. System.out.println in Java and console.log in JavaScript are also library functions. Even "Hello, World" stands on someone else's code.
Standard libraries and third-party libraries
Libraries generally fall into two broad categories.
Standard libraries are supplied as part of the programming language's standard environment. They provide frequently needed functionality. In the example above, we used the math library, a standard library that comes with the language.
Third-party libraries are developed separately, by companies, organizations, or individual programmers. They extend what we can do beyond the standard library.
For example, libraries can help us create websites, display charts, communicate with databases, process images, develop computer games, and build artificial intelligence applications.
Some are small, containing just a few functions. Others provide extensive functionality and thousands of components.
There are millions of third-party libraries. So how do you download them, add them to your application, and manage their different versions?
Each language has its own "app store" for libraries, called a package manager. There is usually a central repository that the package manager connects to in order to download the libraries. Here are the most common ones:
| Language | Package manager | Example command |
|---|---|---|
| Python | pip | pip install requests |
| JavaScript | npm | npm install express |
| Java | Maven or Gradle | a line in the project file |
| C | no single standard (vcpkg, Conan, system packages) | depends on the tool |
With one command, the package manager downloads the library and everything it needs, and it is ready to use.
Why libraries make us faster: a task that would take months, like building a secure login or a PDF generator, can take an afternoon.
Why libraries give higher quality: a popular library has been used and tested by thousands of programmers before us. Its bugs were found and fixed long ago. Code that we write from scratch has not had that luxury.
Open source: the world's largest shared kitchen
Where do all these millions of libraries come from? Most of them come from open source communities: programmers around the world who write code and publish it for free, so anyone can use it, read it, and improve it.
Imagine a giant shared kitchen. Every cook who invents a good recipe pins it on the wall. Anyone may use it. If you find a way to make it better, you can suggest a change, and if the community agrees, the recipe on the wall is updated for everyone. Over time, the best recipes become polished by thousands of hands.
Why would anyone give their work away for free?
People contribute for many reasons. Some want to learn, some want to build a reputation that helps their career, and some simply enjoy it. Many large companies pay their own employees to work on open source, because they depend on it and it is cheaper to improve a shared library together than for every company to build its own.
A word of caution
Using someone else's code also means trusting it. Before choosing a library, a professional asks a few questions:
- Is it alive? When was the last update? Are bugs being fixed?
- Is it popular? Many users means many eyes looking for problems.
- What does the license allow? Open source licenses differ. Some let you do almost anything; others require you to share your own code if you use theirs.
- Is it secure? In 2021, a security hole was found in Log4j, a small Java logging library used by a huge number of systems. Companies around the world rushed to update over a single weekend. One small shared brick, used everywhere, can affect everyone.
Why open source makes us faster and better: it gives every programmer, even a student at home, access to the same high-quality tools used by the biggest companies in the world. It is perhaps the greatest act of cooperation in the history of engineering.
Object-oriented programming: bringing the real world into code
So far, we've learned how to create reusable functions, handle errors, organize code into files, and use libraries.
Now let's explore another major concept: object-oriented programming, often abbreviated as OOP. Object-oriented programming was introduced to let programmers build large applications in a way that is much closer to the real world.
The next chapter will be fully dedicated to this subject, with examples; in this chapter, I will just give a general explanation.
The real world around us is made of entities, like cars, people, and buildings, and each of them has its own data and actions it can perform.
For example, imagine you are building a computer game. The game includes players, monsters, vehicles, weapons, and buildings. Each of these things has information associated with it and actions it can perform.
For example, a player might have a name, a health level, and a position in the game. The player can move, attack, and receive damage.
We could represent all this information using separate variables and functions. But object-oriented programming offers another way to organize the program. It allows us to group related data and behavior together into units called objects.
What is an object?
Think about a car. Every car has characteristics such as its color, speed, and fuel level. And every car can perform actions such as accelerating, braking, and stopping.
In object-oriented programming, we can represent those characteristics as attributes (data) and those actions as methods (functions associated with an object). The idea is that instead of managing the car's information and actions separately, we group them together into a single unit that represents the car.
But more on this in the next chapter.
Putting everything together: from a small program to real software
Let's return to our online shop.
At the beginning, we knew only the basic building blocks: variables, conditions, and loops. With those tools, we could write a small program that asks a customer for a price, calculates the total, and displays the result.
But as the application grows, we need more than individual commands.
We might create functions to calculate prices and discounts.
We could add error handling to deal with failed payments or missing information.
We could organize customer management, inventory, and payments into separate files and packages.
Instead of implementing every capability ourselves, we might use libraries to communicate with databases or process information.
Some of those libraries may come from open-source communities, where developers continuously improve them.
Finally, we might use object-oriented programming to represent customers, products, shopping carts, and orders as objects that hold related information and behavior.