How To Learn Computer Programming
I can’t give complete instructions on how to learn to program here–it’s a complex skill. But I can tell you that books and courses won’t do it (many, maybe most of the best hackers are self-taught). You can learn language features–bits of knowledge–from books, but the mind-set that makes that knowledge into living skill can be learned only by practice and apprenticeship. What will do it is (a) reading code and (b) writing code.
Learning to program is like learning to write good natural language. The best way to do it is to read some stuff written by masters of the form, write some things yourself, read a lot more, write a little more, read a lot more, write some more…and repeat until your writing begins to develop the kind of strength and economy you see in your models.
But be aware that you won’t reach the skill level of a hacker or even merely a programmer if you know only one or two languages–you need to learn to think about programming problems in a general way, independent of any one language. To be a real hacker, you need to get to the point where you can learn a new language in days by relating what’s in the manual to what you already know. This means you should learn several very different languages.
The aspiring computer programmer in today’s world has more learning options than ever before to learn. There are books, online courses with video and interactive tutorials, bootcamps that promise to turn people into software engineers in six weeks (or six months) and the time-honored computer science degree programs at colleges and universities around the world.
And yet, many people who set out to become programmers—whether they take a self-taught path from books or online courses or choose to a bootcamp or university, find that even after rigorous study, still can’t actually program a computer. Even graduates from the top computer science schools in the world still don’t feel like they can actually program or even got their money’s worth from what are very expensive academic institutions. For a hilarious take on this, see this Stanford CS graduate’s assessment of his entire four-year undergraduate program.
As Eric Raymond points out, learning to program is a complex skill. It’s so complex that many people give up on it after a certain point. There are simply a lot of things to know in order to program with any degree of competency.
Learning any complex skill can take a person down a lot of blind alleys and dead end streets. It’s easy to get frustrated and lose hope. The feeling of being overwhelmed is a constant companion on one’s journey towards mastery.
Computer programming encompasses a vast set of skills. Like all vast skill sets, it is difficult to know in what order to learn the skills you need to know.
One of the most common mistakes that I have witnessed in my own journey and I feel that beginning computer education doesn’t address enough is language-independent understanding. When most people set out to learn to program, they usually want to learn one language because they want to do something with that language (or someone presumably more skilled than them told them what language to learn). A person who wants to build websites would want to learn HTML, CSS and JavaScript. Another person who wants to work in in data science, machine learning and artificial intelligence might be interested in learning Python. To build iPhone apps, a person would want to learn Swift. To build Android apps, that person would need to learn Kotlin or Java.
To someone unfamiliar with how computer languages work, the prospect of becoming a polyglot programmer is mind-bending. It’s hard enough to learn one language, but five or six or ten languages just seems impossible.
What most programming educational materials don’t impart to beginners early enough is that almost all modern computer languages contain a finite number of language constructs. And if you learn these constructs in an abstract, conceptual sense, then learning a new language is simply a matter of learning the syntax of that new language and how those language concepts are implemented in a particular language.
Once you have a good general sense of a language’s grammar and syntax, then you need to begin reading code written by experienced programmers in that language and then write your own code.
The underlying concepts of the language are the same in all languages. As Raymond says, “you need to learn to think about programming problems in a general way, independent of any one language. To be a real hacker, you need to get to the point where you can learn a new language in days by relating what’s in the manual to what you already know.”
Let’s explore the concept of language-independent understanding a bit by relating computer languages to something we’re all familiar with—human languages. Every human language has nouns, verbs, adjectives, adverbs and all of the other basic grammatical elements. And all humans, no matter what language(s) they speak, need to be able to say the same things. All people say basic things “hello”, “goodbye”, “how are you?”” and “I’m hungry” as well more complex things such as expressing feelings like love, empathy, anger and sorrow. But the way these concepts are expressed differs in the syntax of the language. The actual words that are used to express those concepts are different in every language, but the concepts expressed are the same.
Like human languages, computer languages all contain roughly the same elements. However, computer languages have a much smaller set of concepts than human languages do. That’s why it’s possible for an experienced programmer to pick up a new language in a matter of days or weeks.
At their lowest, most fundamental level, computers speak in numbers. Computing is mathematical computation. At the higher levels of abstraction that most humans work with, computers “process” text, true and false values, make decisions based on logic, iterate over collections of data elements and perform operations on that data.
With just these few simple concepts, computer languages can model things in the real world such as mail, calendars, accounting ledgers, cars, airplanes, rockets and, as humanity is recently beginning to see, a very simple model of human intelligence itself. Any object that exists in the real world can be modeled by a computer program. Computers can take input from humans, from the world or from other computers, perform operations on that input, and give back results that are useful to humans.
Most people struggle to learn programming. As someone who has also struggled with it, I believe a large part of the struggle is due to beginners not fundamentally understanding the basic elements that are inherent in all computer languages. The beginning programmer sees Python as a completely different thing than JavaScript or C++ and they type in the examples in a book or tutorial without really knowing what they are typing or why they are typing it.
Yes, Ruby, JavaScript and Python are different languages just as English and Chinese and Portuguese are. And each of these languages has its own use cases. But they each contain the same elements. And if you understand these elements, you can then begin the process of reading and writing code that Raymond describes and take the journey towards mastery yourself if you choose.
The Elements of Programming Languages
While every computer language has its unique features, quirks and ideal use cases, pretty much all modern programming languages have the following elements:
Atomic/Scalar/Primitives/Types
In modern computer languages, the lowest level of abstraction that the programmer works with are primitive, atomic, scalar values. These values can’t really be reduced any lower within the confines of a particular language. They are akin to letters in a human language like English.
At their lowest level, computers only really understand 1’s and 0’s of binary. However, binary is difficult for humans to write, read and understand. Modern languages have abstracted the translation of the language syntax to machine-executable binary code through the use of interpreters and compilers.
The primitive, atomic values in modern computer languages are Numbers, Strings and Booleans.
Numbers can be integers (whole numbers with no decimal points), floating-point numbers (numbers with decimals) and less commonly can also be hexadecimal and octal numbers that are used in special kinds of programming.
4, 5, 6 // Integers
3.1, 2.8 // Floating-Point numbers
Strings are how programming languages represent text in programs. In general, strings store text in either single quotation marks or double-quotation marks. Since a lot of computer programs process text, strings are very important. Luckily, a lot of the syntax rules and methods around strings are very similar across languages.
'A string in single quotes'
"A string in double quotes"
Booleans are true and false values. They are derived from Boolean algebra which is named after its creator, the 19th century English mathematician, George Boole. Booleans are most commonly used in conditional statements—if a certain condition is true, execute this code, if the condition is false, execute some other code. Having only two values (true and false), Booleans are pretty much the same across multiple programming languages.
True
False
Variables & Constants
Variables are one of the first things a new programmer learns. A variable is used to store values in the computer’s memory under a unique name so that they can be referenced and manipulated by programs.
The values that can be stored in a variable range from individual characters, to strings of text, Booleans and numbers. Additionally, multiple values can be held by a single variable (more on this in a moment).
Variables are called variables because their values can change over time as the program executes. In addition to variables, programs can also hold constants which are values that cannot change over the life of the program.
Depending on the language, variables are either dynamically (loosely) or statically (strongly) typed. In dynamically typed languages (such as Python, JavaScript and Ruby), a variable simply needs a name and a value.
# Variables in Ruby
months = 12
greet = "Hello, World"
# Python's variable declaration syntax is much the same as Ruby's
months = 12
greet = "Hello, World"
// The let keyword is used in modern JavaScript to declare variables
let greeting = "hello";
let x = 2;
// Older versions of JavaScript use the var keyword for variables
var name = "John";
var amount = 1.20;
In statically typed languages (such as Java, C++ and C), variables must also contain a data type that the variable can hold.
// Java variables with a data type assigned
int currentAge = 35;
String homeTown = "San Francisco";
boolean myBool = true;
In general, most interpreted languages are dynamically typed while most compiled languages are statically typed. There is a lot of debate over which is better but, in general, statically typed languages generally result in faster programs and are less prone to errors than dynamically typed languages are. However, statically typed languages are also more difficult to program in and sometimes require the programmer to think about how the program interacts with the computer’s memory.
Collections
While variables and constants hold individual values, it is often more common that programs need to store multiple values such as a list of names or numbers. In fact, most of the data that computers work with is in some kind of ordered collection.
Collections take us into the realm of what are called data structures in computer science. Since modeling real-world data is most of what computers do, finding the most efficient way to access and process that data is called the study of algorithms.
Computer languages generally use two different language constructs to hold collections of multiple values: arrays (which, depending on the language are sometimes called lists) and associative arrays (which again, depending on the language are also called hashes, dictionaries or maps).
Arrays
Arrays are ordered collections of values that are most often separated by commas. In some languages, arrays may be called lists. They are often contained within brackets:
# An array in Ruby
a = [1, 2, "three", 4]
// An array in JavaScript
let a = [1.1, true, "tim", 4];
# Arrays in Python are called lists but have similar uses
l = [0, 1, 2, 3, true, "list"]
The individual elements in an array are accessed by referencing the element’s position in the array. Most arrays are zero-based indexes so the first item in an array is actually zero and not one.
Since a lot of what is done with arrays is common across all languages, arrays have all kinds of methods that are built-in to most computer languages. In fact, many of the method names are actually the same across different languages. So learning the method names to do all of the various operations on an array for one language often carries over to other languages.
Computer programs mainly do four things with arrays:
- Read: Retrieve a value or a range of values from within an array.
- Search: Find a value or a range of values from within an array.
- Insert: Add one or more values to different parts of the array.
- Delete: Remove one or more values from the array.
Associative Arrays: Hashes, Dictionaries & Other Key-Value Collection Types
Associative arrays are, depending on the language, also called hashes, dictionaries, maps and occasionally objects (although objects generally have a different meaning in most languages). The main difference between plain old arrays and associative arrays is that associative arrays are lists of key-value pairs instead of numerically indexed lists. They are often enclosed in curly braces. To retrieve an element from an associative array, we refer to its key to access the element’s value.
# Associative arrays in Ruby are called hashes
states_hash = { :pennsylvania => "PA",
:delaware => "DE",
:maryland => "MD",
:virginia => "VA"
}
// Associative arrays in JavaScript are called objects
let person = {
name: "Tom",
age: 35
};
# Associative arrays in Python are called dictionaries
cart = {'item': 't-shirt',
'size': 'L',
'color': 'black',
'quantity': 1}
Associative arrays are a very common data structure when working with web programming where web applications usually perform persistent storage of data using databases on the back end of a web application. Associative array keys represent the column names of a database table while the values store the individual values of each database record.
Control Flow Statements
Conditionals
In computer programming, we need our programs to make decisions based on certain criteria. If this condition is true, execute a certain block of code. If the condition is false, execute a different block of code (or simply do nothing). To build this decision-making logic into programs, the programmer uses conditional statements. Conditional statements are a subset of what are known as control flow statements that pass the control of the program to various parts of the code depending on the evaluation of the statements.
If and If-Else Statements
The most common conditional statements are the if and if-else statements. These statements are present in pretty much all high-level, modern programming languages with minor syntax differences.
# Basic If-Else statement structure in Ruby
if condition
# execute some code
else
#execute some other code
end
Switch Statements
If a program has a lot of conditional statements to evaluate, some languages provide another way to evaluate a larger set of conditions called a switch statement. Rather than having to chain a bunch of if-else statements together, some languages allow the programmer to write conditions more efficiently using a switch statement. Depending on the language, the syntax of switch statements can look slightly different. But the concept is generally the same.
// Switch statement syntax in JavaScript
switch(expression) {
case value1:
// code to be executed
break;
case value2:
// code to be executed
break;
case value-n:
// code to be executed
break;
default:
// code to be executed when no cases match
break;
}
Loops & Iteration
Programs often need to iterate over a sequence of values and perform some sort of operation on some or all of the values such as reading them, altering the values, or searching for some value or set of values.
To perform these iterations, a programming language needs to have built-in iteration functionality. In all modern languages, loops serve this purpose. In addition to conditional statements, loops are another form of control flow statement.
While Loops
Perhaps the simplest loop structure is the while loop. The while loop simply sets some initial criteria for how many times the loop should run and, in the body of the loop, what operations should be performed on each iteration:
# A while loop in Ruby that counts from 1 to 10
n = 1
while n < 11
puts n
n += 1
end
# Output: 1 2 3 4 5 6 7 8 9 10
// The same while loop written in JavaScript
let count = 1;
while (count < 11) {
console.log(count);
count++;
}
# The same while loop written in Python
x = 1
while x < 11:
print(x)
x += 1
For Loops
Perhaps the most common looping structure is called the for loop. In a for loop, the opening loop statement has two or three components: an initial variable name for the loop, how many times the loop should run, and (if applicable) by how much the loop should increment by on each iteration. Inside the body of the loop is what actions should be taken on each iteration of the loop:
# A for loop in Ruby that counts from 1 to 10
for i in (1..10)
print i, " "
end
# Output: 1 2 3 4 5 6 7 8 9 10
// The same for loop written in JavaScript
for(let count = 1; count < 11; count++) {
console.log(count);
}
# The same for loop written in Python
for number in (range(1, 11)):
print(number)
Other Loops
The while and for loops are fairly universal across most programming languages. However, there are other looping constructs that are specific to individual languages are groups of languages. For instance, another common looping construct is the do-while loop.
Some languages (such as Ruby) have an iterator class that is separate from loops with special keywords that mimic loop functionality allow for more expressive writing of loop-type constructs.
Functions, Methods & Subroutines
Functions, methods and subroutines are basically three different names for language elements that do very similar things. For this discussion, we will just call them functions since that is how they are normally referred to. Depending on the language and context, functions can also be referred to as methods and subroutines.
Functions make it possible to write one piece of code that can be reused multiple times in a program. They are little bundles of code that can take data from other parts of a program, perform an operation on the data, and then return the result back to another part of the program. They can almost be thought of as little cogs in a machine that have one unique, little function (pun intended) in the working of the overall machine.
Functions allow the programmer to break a larger program down into small, manageable parts. This serves multiple purposes. For one, having each small part do one thing allows us to organize our code in a way that is easily understood. Additionally, the modular nature of functions enables us to avoid duplicating code which makes our program easier to change and allows us to diagnose errors faster.
A function should do one, small, easily-definable thing and no more. Keeping functions small is crucial to writing programs that are loosely-coupled and can be changed easily as the overall size of a program grows.
Depending on the language, functions are written in different ways. In general, functions usually have some kind of keyword that defines them as functions, followed by a name for the function and often followed by parentheses which may contain one or more parameters. Parameters are values that can be passed into the function as arguments when it is called by another part of the program.
# A simple Ruby function (method) that has one parameter called param
# Methods in Ruby start with the 'def' keyword and end with 'end'
def talk (param)
puts "Hello #{param}!"
end
# Call the method and pass in "World" as the argument
talk ("World")
# Output: Hello World!
/* A simple JavaScript function that also has one parameter called name
Functions in JavaScript usually start with the 'function' keyword */
function sayHello(name) {
console.log("Hello", name + "!");
}
// Call the function and pass in "World" as the argument
sayHello("World");
// Output: Hello World!
# A simple function in Python that has two parameters
# Functions in Python start with the 'def' keyword
# A colon ends the first line of the function
# Additionally, the function body must be indented
# The customary Python indentation is 4 spaces
def hello(x, y):
print(x + " " + y)
# Call the function with two arguments
hello("Hello", "World")
# Output: Hello World
While these are some simple examples, functions are a fairly large topic and are an extremely important part of several different programming paradigms. Procedural, functional and object-oriented programming paradigms all make heavy use of functions. In fact, in the procedural and functional programming paradigms, a computer program is really just a collection of functions and variables.
Classes & Objects
The use of classes and objects in programming is known as object-oriented programming (OOP). The object-oriented paradigm dates back all the way to the 1960s to a language called Simula. In the 1970s, the ideas behind OOP were refined with the creation of the Smalltalk language.
Object-oriented programming became popular in the 1980s with the creation of C++ and then later in the 1990s with the creation of Java. Today, OOP is still widely used and is supported in a number of programming languages such as Ruby, Python, JavaScript and PHP.
Object-oriented programming became widely-used because it allows humans to abstract tangible things in the real world into computer code. Computer languages at lower levels require that humans think more like the machine than a human. With OOP, humans are able to model real-world concepts as classes and objects which makes the creation of larger and more complex programs significantly easier than with lower-level languages.
In OOP, the programmer creates a class which is akin to a blueprint or factory mold. Classes can hold properties (data) about a thing as well as methods (functions) about what actions that thing can take.
Once the class has been created, the program can create instances of the class, otherwise known as objects. To use a loose metaphor, if a class is akin to the concept of a person, then individual people (Bob, Jane and Tom) are objects. That is to say, Bob, Jane and Tom are instances of the person class.
In a computer program, objects can hold state which consists of properties of the object as well as the current value of those properties. For instance, Bob has an age property with the current value of 35. The fact that a person has an age won’t change but the value of the age property may change over time.
Objects also have behavior and interact with each other via methods (i.e. functions inside of classes). The methods of an object can change the state of the object and other objects. This is essentially what a computer program written in the object-oriented style does.
# A simple Ruby class
class Person
# The attr_reader method is a shortcut accessor method
# Lets the object return the values assigned to it
attr_reader :firstname, :lastname
# The initialize method sets a new object's state when created
def initialize(firstname, lastname)
@firstname = firstname
@lastname = lastname
end
# The greet method can be called on objects
def greet
"Hello, my name is #{firstname} #{lastname}."
end
end
# Create a new object (instance) of the Person class
# Return its values and call the greet method
p = Person.new("John", "Smith")
puts p.firstname # John
puts p.lastname # Smith
puts p.greet # Hello, my name is John Smith.
// A simple JavaScript class
class Person {
// Constructor method initializes new objects with assigned values
constructor(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
/* The greet method is just a function inside a class
No 'function' keyword needed */
greet() {
console.log("Hello, my name is", this.firstname, this.lastname)
}
}
/* Create a new object (instance) of the Person class
Return its values and call the greet method */
let p = new Person("John", "Smith");
console.log(p.firstname); // John
console.log(p.lastname); // Smith
p.greet(); // Hello, my name is John Smith
# A simple Python class
class Person:
# The initializer method sets the state of new objects when created
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
# The greet method for the current object
def greet(self):
print("Hello, my name is", self.firstname, self.lastname)
# Instantiate a new Person instance
p = Person("John", "Smith")
# Return its values and call the greet method
print(p.firstname)
print(p.lastname)
p.greet()
Object-oriented programming has several concepts that should also be understood. Perhaps the two most important are:
• Inheritance: The idea that classes can have a hierarchy similar to a family tree where child classes can inherit data characteristics and methods from their parent classes.
• Encapsulation: The idea that an object can hide information about itself from other objects but also share other attributes freely. In object-oriented languages, encapsulation is usually manifested in public, protected and private attributes and methods of a class.
Object-oriented programming and design, despite its ability to model real-world concepts and make large programs easier to write and understand, can actually get quite complex. OOP contains a lot of concepts that are well beyond the scope of this post. So becoming really competent at OOP takes quite a bit of study.
However, the object-oriented paradigm is prevalent in a large part of modern software for both native and web applications. So it’s quite important to become very familiar with it. Luckily, while the syntax of OOP differs among different languages, the concepts are largely the same.
Final Thoughts
When I first started programming seriously, I wish that someone had pointed out to me sooner about language-independent concepts and universal language constructs in computer programming languages. It would have made my journey to learn multiple languages a lot easier and faster.
All of these concepts are totally obvious to experienced programmers. But to a novice struggling to learn their first language, having a sense of the overall landscape of computer languages can be incredibly helpful. A lot of language-specific tutorials don’t really point out these elements in an obvious way.
Frameworks and Things That Don’t Change
Much of modern programming today involves the use of frameworks and libraries to build applications or do various things. These libraries and frameworks involve using code that other people have already written rather than programmers having to write all of the low-level code themselves. In other words, libraries and frameworks save the developer a lot of time by making routine and time-consuming tasks much easier to implement.
Often, these frameworks and libraries have domain-specific languages (DSLs) that are based on traditional programming languages (sometimes called general-purpose languages (GPLs)) but extend them with custom syntax. So in addition to knowing the basic syntax of the underlying language, a programmer must also learn the DSL of the library or framework.
In fact, many programmers today don’t even bother to learn the underlying syntax of a language very well but simply learn the DSL of the framework or library. The problem with this is that frameworks and libraries are numerous and come in and out of fashion over time. The new hot framework of just a few years ago is often replaced by a newer, hotter, hipper framework of today. As a result, the programmer ends up having to learn a whole new syntax with every new framework and library that comes along and promises to solve all of their problems. Constantly chasing new frameworks and existing framework updates results in a phenomenon known as framework fatigue which has been known to cause programmer burnout and disillusionment.
There is no doubt that frameworks and libraries can save a programmer a tremendous amount of time and effort. However, not understanding the underlying languages that these frameworks and libraries are built in is a big mistake. When you know the languages that all of these frameworks and libraries are built in, then learning a new library or framework is much the same as learning a new programming language when you understand the universal language constructs outlined in this post. If you understand the concepts of the underlying language, then learning a new framework or library is simply a matter of spending a few days/weeks learning the syntax of the library or framework.
In summary, frameworks and libraries can change dramatically every few years. But the underlying languages they are built in usually change very slowly and can often stay the same for decades. Focusing on the things that don’t change that much (the languages) makes learning the things that do change frequently (frameworks and libraries) much easier.
I have two older brothers who were both computer science majors in college in the 1980s. Although I was exposed to programming at an early age, I didn’t really study it seriously until I was well into my adult years. I first programmed in elementary school and made several other attempts to learn it up until I graduated from college in the late 1990s. Still, a lot of these concepts didn’t dawn on me until I was quite a long way into my programming journey.
While programming is still a difficult skill to master, having a sense of the overall landscape of modern computer languages has made a huge difference in my journey. I hope it will do the same for you.