The first principles of programming

Learn programming
Credit: Wikimedia

I recently wrote a 80-page guide to how to get a programming job without a degree, curated from my experience helping students do just that at a leading data science bootcamp. This excerpt is a part where I focus on getting all the way to the first principles of programming, a skeleton that will help you learn more effectively. This is the approach to learning that Elon Musk favors — and it’s something that can help you get back to the foundation of learning programming effectively. 

1- Programming is about manipulating data

At its core, programming is about moving data and playing with it. When you send login credentials to a web server, or when you get your profile picture loaded, that’s code sending data back and forth. If you can recognize that moving data is the foundation of programming, you can understand the basement from where you will build your house.

2- Programming is like writing — you want to be as clear as possible

When you’re writing code, you may think of it as an individual activity. Nothing could be further from the truth, despite every stereotype Hollywood has thrown at the programming archetype. When you’re programming, you’ll want to imagine that you’re writing for an audience. Programming is a collaborative activity, one that often involves working with close teammates or other collaborators.

3- Know the different types of programming

Do you think of programming as one generic blob or can you differentiate it into specialized parts? Saying that you want to be a programmer is a bit like saying you want to be an engineer — the devil can be in the details.

We’ll go into more details about the broad technical paths you can take as a programmer in different paths later on, but for now, we can distinguish some broad differences.

The largest difference in types of programming can be thought of as the difference between “front-end” and “back-end” programming. Front-end involves manipulating what a user sees directly: think of the interface you see when you log in to any web interface. The back-end is all of the magic that happens sight unseen — the way servers process your password and grant you access to all of your data.

The different paradigms of programming

There are also different types of programming paradigms: different ways to express logic, and different functionalities for each programming language that at an aggregate level, can be summed up into categorical differences.

Here is an overview of the major paradigms in programming:

  • Declarative. Declarative programming is very simple and plain. It expresses the logic of a particular computation without specifying its flow. The easiest way to think about it is a programming language that declares what task is being done rather than how it should be done. Examples of this include programming languages like SQL, whose syntax is focused on explicitly specifying exactly what you want as opposed to specifying how it’s done (ex: the SELECT command which selects data). The underlying steps behind the SELECT query do not have to be explicitly defined for the machine to act upon its underlying logic.
  • Imperative. Imperative programming focuses on how a task is being done rather than what is being done, unlike the declarative model. Much like how in the imperative mode of language where commands are given, the imperative programming paradigm describes to machines how they should carry out a task. Imperative languages include languages such as Java, JavaScript, and Ruby, though all of them also have object-oriented logic as well — and most of them are multi-paradigm languages that are compatible with a variety of programming paradigms.
  • Functional. Functional programming is based on mathematical functions. While here too, commands are meant to specify how routines are carried out rather than what routines are carried out, unlike in the imperative paradigm, the state of a current program cannot be affected incidentally: what this means in practice is that you can have functions without return calls, since the program state will remain constant. Functional programming is emphasized in academia with languages such as Lisp and Clojure prominently supporting functional programming as a paradigm.
  • Object-oriented. The dominant programming paradigm since the 1980s, object-oriented programming involves building objects with data attributes and programming subroutines known as methods which can then, in turn, be invoked or modified. Languages such as Java, Python, C, C++, PHP, and Ruby are all principally object-oriented. Critically, unlike imperative or functional programming, the concept of inheritance and code reusability are firmly entrenched in programming objects which can persist either as classes (the definition of how a set of objects is defined, and what data they can carry) or objects themselves (which often correspond to real-world objects and a collection of attributes associated with them).

There are also programming paradigms, mostly a bit outdated or a bit theoretical, that revolve around procedural programming, logic programming, and symbolic programming. Research those if you have the time, and compare and contrast!  

4- Programming is about forced simplicity

Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.

-The Zen of Python

One of the founding principles of effective programming is a sort of forced simplicity that becomes natural with time and iteration. Perhaps perfectly summarized in the Zen of Python, simple is better than complex — and complex is better than complicated. Much like good writing, which sometimes requires focusing on the right ideas and cutting out as many unnecessary words as possible, good programming means keeping the underlying logic expressed simply so that it can be readable to others and perhaps most importantly, your future self!

5- Small efficiencies lead to large gains

When you’re dealing with a machine that can perform complex operations in a matter of seconds or sometimes microseconds, it can be hard to comprehend from a human perspective exactly how to manage the efficiency of such a system. Humans are notoriously bad at exponential thinking and programming depends upon that very strand of thought. A few microseconds of difference in one operation can mean a difference of thousands of hours when extended to a chain of operations that extends past the trillions.

There are three concepts here that can help you manage that complexity.

Time complexity

Become familiar with the concept of time complexity in programming and specifically big O notation. Put simply, Big O notation maps out a pattern of how an algorithm will respond to a given set of inputs.

O(1) algorithms react the same regardless of what input size they’re fed. You could input one value or a trillion: — it doesn’t matter, the algorithm will process at the same time. A common example of this is the return or print algorithm in most programming languages.

O(N) algorithms react linearly to the inputs they’re given.  A million data points? Expect it to run a million times slower than with just one.

O(N2) algorithms react exponentially to the inputs they’re given. Every input gets squared as it’s processed. Think of an algorithm that has to look over data twice on each iteration. With a dataset that’s twice as large as another, you’ll quickly see an exponential burst of time when it comes to how slowly that algorithm is going to deal with different sized datasets.

This goes on in a variety of configurations: this tutorial has a fuller list and a more comprehensive explanation. The takeaway is that you should always strive to use algorithms that scale as linearly as possible, otherwise large datasets will become unmanageable.

Modularity

One of the greatest things about programming is the ability to extend logic that has been saved before. You don’t have to reinvent the wheel each time: if you’ve built a component that works, you can call upon it at any time.

This simple concept of having pieces of code saved that can be called upon like a mix of components is called modularity, and it is an essential time-saving device for individual projects. Modularity also allows you to save time in collaboration. You can work with different people on a complex problem and work on a single piece then have your module interact with those built by others to form a larger solution.

Grasping the nuances of how to make your programs modular will not only save you time, it will make your programs stronger and more readable, and less dependent on large blocks of code that can prove to be riddled with bugs.

Shortcuts

Use shortcuts whenever you can! Programming efficiently involves putting your thoughts and logic into machine form as quickly as possible. This table of keyboard shortcuts will help you speed yourself up. Games such as TypeRacer will help you improve your words per minute count when it comes to typing, allowing you to transfer your thoughts more quickly into code.

Mastery of shortcuts will allow you to build things quicker and see results sooner rather than later.

6- Practice makes perfect

It’s often said that there are two ways the human mind views growth: either the fixed mentality or the growth mentality. In the fixed mentality, human growth is a function of destiny: no matter how hard you try, you can never grow beyond what innate factors have prescribed for you. The growth mentality is the total opposite of the fixed mentality: here, your growth is only limited by time and your will to do something great.

Nobody is naturally born a better programmer than somebody else. You have to work hard and put in the hours if you want to improve your programming skills.

This extends to programming interviews, a necessary evil. The bar is much higher in those than the normal conditions you’ll have gotten used to: ample time and plenty of resources such as StackOverflow to help you along the way (more on that later). A programming interview is designed to stress test you. You’ll have to get used to practicing a form of programming under constraints, both natural and artificial: do timed sets of problem-solving, and rely on nothing more than paper to sketch out a few algorithms.

7- Create programs that are flexible to different needs.

One of the largest difficulties in programming is the need to create things that can flex and which don’t break even under what are called “corner cases”: different uses of your software that may challenge the extreme limits of the variables you’ve set. The easiest corner case to think of is an app that has gotten very popular and must support thousands of users at once. Even the most elegantly crafted code will begin to strain as that happens.

8- Build useful things. Have empathy and understand the problems of your users.

Most people forget that technology isn’t just a skill to use for fun. Technology is a means to an end. Don’t forget that you’re not building for the sake of building: you’re building for an end user or to solve a meaningful problem that cannot be addressed without technology. Know when to build something with code, and know when you don’t have to.

If somebody has already built the wheel, there’s no need to reinvent it. Plenty of people have built solutions to address how to store sales contacts — but are there problems out there that remain unseen? Tons! And each could be the foundation for a successful project.  

Build useful things with your programming skills. It’s the best way to use your skills for good and to highlight them to others.

Now, what does useful mean? That’s the crux of the problem. In a world filled with apps for every possible luxury and consumer need, it can be hard to see exactly what useful means. In this context, it is important to practice empathy and to diagnose real problems that can be helped with technological solutions. This, notably, does not have to be confined to problems that can be solved for profit. There is much room for technology for social good initiatives or for technologists to solve social problems. This case study of data science work for international development/basic income charity GiveDirectly provides a great example.

In order to become the most impactful technologist possible, it’s important to understand what problems people have — it’s important to know when and how to ask the right questions, how to listen and to uncover the underlying problems everyday people face. With the right amount of empathy and perspective, you can build maximally useful things that help solve problems for others.

9Take advantage of as many resources as possible, and give back when you can

Go onto communities such as Hacker News and Quora. Look up questions on StackOverflow — and even ask a few yourself if you’re stuck on anything! Look up awesome Github repositories that contain all of the resources you’d need to learn one particular topic: see, for example, Awesome Python for a list of curated resources in the Python space.

And don’t be afraid of failure. You’ll be learning along the way, practicing your skills and becoming better with every passing day regardless of the outcome.

Want more material like this? Check out my guide to how to get a programming job without a degree.

This post is part of our contributor series. The views expressed are the author's own and not necessarily shared by TNW.

Read next: Worlds collide as Thanos arrives in Fortnite