A free, comprehensive course.
Michael's shortest path to full-stack development.
I'M INTERESTED
Introduction
Quick Summary
This is the course I'd want if I
were new to programming and web development.
-
I'd want a map, a guide. I'd want a clear overview so I can
see how it all fits together. What is similar across
programming languages? What differs? What is the most logical
order to learn things to get a good feeling for how it all
works?
-
I'd want to know that the course covers everything
(or almost everything) that I need to know to have
a sense of mastery and be confident that I can
learn specialized details on my own, now that
I have a solid foundation.
-
I wouldn't care if the way I was being taught to
do things was the absolute best way to do them. I'd
just want it to be a reasonable way to do them,
especially for the smaller projects I'm likely to work on first.
-
I'd want the course to at least mention most or all of the important
concepts and tools, so I know what's out there, what things
I might want to learn more about or practice with more,
what the key tools are and what they do.
-
I'd want a detailed explanation of the
points that are the hardest and most
commonly misunderstood or under-emphasized.
On the other hand, for the simpler concepts I'd
want things to stay brief -- to stay on the
"shortest path" -- knowing that they will
become clear in the example projects, or that I
look them up as needed.
-
I'd want to write real programs as soon as possible.
I'd scan the table of contents closely to see what I'm
going to be able to make. I'd like good suggestions for
how I can extend these projects on my own so I can
prove my competence to myself.
About This Introduction
This introduction gives a historical perspective on how we arrived
at the current explosion in programming languages, frameworks, and
libraries, and how there might seem like an impossible amount to
learn for someone new to programming, and web programming in
particular.
It then goes on to describe how this course helps you manage
this avalanche of choices by focusing on the fundamentals that apply
across languages, while still giving you real, concrete
practice with multiple programming languages and web technologies.
The Tyranny of Choice
In 1982, at age 14, I taught myself to program on an Apple ][ computer
with 64k of memory, of which 48k was available for programs.
(The image of the two hikers above, in a highly compressed,
lossy JPG format, is just about 48k.)
I programmed in BASIC, and when that proved too slow to make
"real" games, I learned assembly language.
There was no internet. Learning was from books. And if you wanted to
use a clever piece of code that someone else had written, you copied
it character-by-character from a book or magazine article. It was hard, but
it was also easy. There were so few choices that it didn't take long to learn
everything, and programmers spent
almost all of their time being creative with the limited
tools they had, instead of trying to keep up with an avalanche of
tool choices.
In 2004, when I started a job at Amazon and was coming up to speed,
belatedly, on web programming, things were completely different.
Front-end programming was in JavaScript. Back-end programming and offline processing
was in C, C++, C#, Perl, Java, or PHP. Python was becoming popular.
Aside from all these language choices, two even larger trends were growing, making
things both easier and harder for programmers. First, lots of code examples and code
libraries were becoming available. C++ programmers were expected to know the
Standard Template Library, which offered stable, optimized implementations of
common data structures and algorithms. (Java and C# had similar built-in libraries.)
Specialized libraries of all kinds
offered code to solve hard problems like the internals of 3D games,
matrix and other math computations, geometrical algorithms, XML processing,
data storage, and many others.
Secondly, more and more elaborate web frameworks were gaining popularity. Starting from
simple substitutions of values into HTML pages with early versions of PHP,
frameworks like ASP.NET and Ruby on Rails
added more and more server-side features to handle web requests according to common
patterns and best practices.
The idea was that following such patterns avoided
reinventing the wheel, prevented errors, and made codebases understandable to anyone
familiar with the given framework. But for programmers, frameworks also meant more
to learn, and more to debug if the framework wasn't doing what you expected.
In the last 20 years, libraries and frameworks have continued their explosive growth.
Programming vs. Shopping
As a new programmer or web developer, it's hard to know what
language to focus on first. You hear that some languages are harder, some easier.
But what do employers want? Plus, the choice of language determines the available
choices for web frameworks. There are mature web frameworks based on Python, PHP, C#, Node.js
(back-end JavaScript), and other languages.
What are the pros and cons? Which would make you most employable?
In addition to the major choices of language and framework, there's also
a seemingly infinite list of libraries for doing any significant piece
of work: libraries for caching, message passing, XML and JSON parsing, reading
and writing graphics files, doing math and geographic calculations,
working with dates and times, and much more. In some
cases, languages come with certain libraries built in. In other cases you link your
program with a library from a third party. Because it is a common stumbling block to build and
integrate third-party libraries correctly, there are elaborate package-management
tools for gathering and building these dependencies and keeping them up to date as
the library makers add features and fix bugs.
In short, modern programming can feel more like shopping than creating. And
it makes sense: why reinvent the wheel if someone has already made a library or
framework that does the thing perfectly and has been thoroughly tested? On the other
hand, this can lead to paralysis, weighing and reconsidering each option,
searching for some perfect option that might exist out there and has all the features
you need, reading reviews, watching tutorials. It can
go on forever. Any time you want to do anything, there is a pause to wonder,
should I shop first? Should I find an existing framework or library?
Should I use a framework or library that meets my needs but is bloated with
stuff I don't care about? Or should I just write it myself (possibly with AI assistance)
and risk making something incomplete and with errors?
Tutorial Hell ("Okay, but how do I actually write programs?")
Tutorial Hell refers to a state you can get in from reading or
watching too many tutorials. In one video after another, a kind
expert guides you through the features of a language or framework.
They show
you how a for loop works, or how to use a certain CSS property.
Or they
patiently solve a data structure/algorithm (DSA) problem.
The videos are accurate and generally well made.
The teachers are patient and clear. You understand it all.
And yet, when it comes to putting it all together in a simple program,
you don't know where to start. Perhaps all the information you've learned
is in your head, but
disjointed. Perhaps you don't quite know how to set up the tools.
More fundamentally, you feel you don't know how to "think like a
programmer." So you watch another tutorial.
About this Course
I feel badly whenever I see that learning programming is not fun.
For me, programming always was and still is fun. When I started
programming, I didn't care if I was doing it "right" or what
others might think of my code: I just had things I wanted to make
and I was more than happy to make them in Applesoft BASIC, which in
hindsight is an incredibly deficient language. Still, I took
pride in iteratively improving my code, and I learned a lot
about programming in the process.
Today, because there are so many tools and so many "best practices"
imposed either by the tools, or by the opinion of experts, it can
feel like there is very much a "right" way to program, and
understandably learners want to know the best language to learn,
the best framework to use, and the best/right programming
techniques. And I know you're impatient to get through it so
that, knowing all the proper tools and techniques, you can
finally get creative, build things you want to build, or work
at a job where you feel mastery as you help create the future.
The goal of this course is to help you get there by following
what is, in my opinion, the shortest path.
Even though I
call it short, you might feel
it's actually not so short, because I spend a lot of time making
sure you get a solid grounding in what I consider the most
important concepts. If you look over the table of contents,
you'll see that some of the topics
have an infinity symbol

in the upper-right corner, and some
don't. The ones without the infinity symbol are more
factual. They involve novel concepts that can be tricky to
understand, but once you get them, you get them.
On the other hand, the infinity symbol means,
"This topic is one you never really finish
learning." These are topics where you will continue to
practice throughout your career. I have tried to give
enough, and deep enough, exercises and projects in these
topics to give you a solid start.
There are also a lot of concepts and tools I don't cover in detail,
because I don't think they are very important for you right now.
In some cases there are tutorials
out there than can explain them well, if you are interested.
In other cases, they are important things to learn, but they are
not required to get things done. I think you can learn about
them after you get some mastery over "plain programming."
To summarize, I hope, first, to give you the facts and basic concepts
as quickly and efficiently as possible, focusing especially on
those that I found confusing when learning them. Second, for the
more difficult, more subjective, more creative skills, I hope to
give you a solid foundation and a feeling of competence.
My Opinionated Approach
This course reflects some important biases I have, opinions that are
not necessarily shared by other programmers and teachers. Please
consider these carefully when deciding whether or not to
embark on this course.
-
I spend a lot of time up front on the details of how data
is stored in computer memory. I think it's important that
when you see int n = 1000; or
char s[100] = "Hello World";
or when you're working on a data structures problem,
you have a clear idea of how the values are stored in memory.
When you think of a pointer or reference, I want you to understand
that it's a 64 bit value holding the address of a memory location.
Because C lets you work with "raw memory" more naturally, I
introduce C in the very first topic, which is uncommon.
-
I see programming languages as more
alike than different. I tend to avoid, or at least
to de-emphasize,
the features of languages that make them different from one
another. In this course, we use a few different languages
because I think it's good to be familiar with them, but I avoid
the quirks of each language when possible. Many people
think if you're using Python, for instance, you
should do everything as "Pythonically" as possible. I'm on the
other side, where I feel it's best to stick with idioms that
work across languages, when possible.
-
In addition to sticking to common idioms, I also tend to avoid
parts of languages that introduce unnecessary complexity.
As an example, the way I program in C++
avoids a lot of the complexities that people often say make it
a hard or dangerous language.
-
There are points of style that I feel differently
about than many people, especially in the context of
smaller programs, which in my experience is most programs.
These affect choices for
variable names (I think one-letter names are often best),
use of global variables (using them will not irrevocably
cripple your programming skills), and function length (I think
in many cases very long functions are perfectly acceptable
and lead to simpler code).
-
For general code formatting, and JavaScript formatting in particular.
It is common to see code like this:
myButton.addEventListener('click', () => {
messageDisplay.textContent = 'Button clicked!';
setTimeout(() => {
messageDisplay.textContent = 'Timeout!';
myFunction(3, 4 * (5 + 2));
}, 3000);
})
I find that hard to read, and I think it's especially hard for a beginner
because so much is going on in those lines. I would format it as:
myButton.addEventListener( "click",
function()
{
messageDisplay.textContent = "Button clicked!";
setTimeout(
function()
{
messageDisplay.textContent = "Timeout!";
myFunction( 3, 4 * ( 5 + 2 ) );
}, 3000 );
}
);
or even
myButton.addEventListener(
"click",
function()
{
messageDisplay.textContent = "Button clicked!";
setTimeout(
function()
{
messageDisplay.textContent = "complete!";
myFunction( 3, 4 * ( 5 + 2 ) );
},
3000
);
}
);
For me, the last one is much clearer. Every open parenthesis
matches a closed parenthesis with the same indentation.
The callback functions are called out as
function.
The parameters to each function are indented at the same level.
The only reason I can see to prefer the original formatting
is to save some vertical space (seven lines in the original version
becomes 12 in the second version, and 15 in the final version), so
you can see more of your code on your screen at once. I don't
think that's a good reason for making the code harder to
read.
As a side note, you see that in JavaScript I use double quotes instead of single.
I do this because double quotes are used in other languages like
C, C++, Java, and C#. I'm constantly switching between JavaScript
and C++ or Java, so it's easiest for me to stick with double quotes.
Another side note: you see that I use additional spaces in
expressions like myFunction( 3, 4 * ( 5 + 2 ) ). This makes
the expression more readable to me than when everything is
more crammed together.
-
Spaces vs. tabs. Almost everyone these days uses spaces instead of tabs
to indent code, and in some languages like YAML it's actually
illegal to use tabs. The reason people give for
spaces instead of tabs is that your code will always look the same,
whereas with tabs one editor might treat a tab as worth four spaces
and another editor might treat a tab as eight spaces. In my experience,
almost all editors these days treat a tab as four spaces by default.
But in any case, I prefer tabs because you're not likely to insert an
extra tab and not notice, whereas inserting an extra space is much
easier.
-
Indentation amount. I see a lot of code written with
two-space indentation instead of four (see for instance the first version
of the code above). I personally find it easy to
lose track of indentation levels when there are only two spaces. Screens
are big these days, even on laptops, and the amount of horizontal space
you gain by using two space indentations is not much, compared to the
potential confusion it introduces. So I use tabs of size four spaces.
-
I don't cover web frameworks, except
at the very end, to give a brief guide to what's out there.
Here's why:
-
I think there's enough to learn, and enough
anxious shopping, without throwing in the question
of which web framework a beginner should learn.
-
If I covered one or two web frameworks, it would
add a lot of bulk to the course, and they might not
even be the frameworks a given employer cares about.
-
Web frameworks are more useful for larger websites
and teams of programmers. They are less important for
small websites with just one programmer, and in
my experience, most websites
are indeed small and written by one programmer.
You probably do need an HTTP library to make a website,
but you don't necessarily need a framework.
-
Frameworks do a bunch of stuff for you, and they
let you make a default website with "just a few lines
of code." But I don't think doing that is especially
enlightening. When you've done it "by hand" yourself, you can
understand what the framework is doing for you, and
that's more valuable.
-
There are lots of good tutorials out there for each
framework. When you want or need to learn one, you can.
-
I don't do strict "test-driven development,"
which is the idea that you make the tests first, and then write
the code to pass those tests. That way, you are sure your code
works from the start. It sounds good, many programmers swear
by it, and some companies require it.
The problem is that writing those tests is very time-consuming,
and when it comes to user-interface code (the look and positioning
of windows, buttons, animations, and so on) it can be very hard to
do. It can also be hard to write tests for larger units,
large parts of your program as whole, which often requires you to
write "mock" or "harness" versions of the other parts, so that the
part being tested can operate as if those other parts existed,
whereas in fact they are fake versions, present only to set up
certain test scenarios.
On the other hand, it is easy to write unit tests for
individual functions or groups of functions that compute values.
For instance, if you write a function
that is supposed to convert a time from AM/PM format to 24-hour
format, it is easy to write tests to confirm that the input
"3:30 PM" gives the output "15:00", and that special cases like
converting "midnight" to "00:00" also work.
There are examples of that function-input-output style of
unit testing in this course.
Some Answers
To answer the two questions we started with:
-
Which programming language should you learn first?
Answer: A few of the popular ones, they are all very similar.
Obviously you have to actually pick one language to write
your first lines of code, but by focusing on the parts
that are the same across languages, you will be able to
learn multiple languages quickly. You can get fancy and
learn the idioms specific to each language later. There
are only two basic programming language syntax styles
for mainstream languages: Python has its own style, and
everything else looks basically like C. The more import
issue is how each language handles memory management,
and we cover that in detail in this course.
-
Which web frameworks should I learn to start?
Answer: None of them, you can make websites without them.
You can learn them later for a job, or out of curiosity for yourself.
For now, we are on the shortest path. If there's something that a
web framework does that you want, look for a library instead. Most
importantly, you will want an HTTP library, and in this course
we will see what various languages offers for that.
The real work in learning programming
is what I call "plain programming," along with
a bit of "data structures and algorithms." For web
development, there is a huge amount of factual information to
learn, especially about page layout with CSS. But almost nobody
fails to become a front-end programmer because of CSS. But they do
fail because of JavaScript, because they never really got good at
plain programming. I try my best in this course to really help you
learn to "think like a programmer" in any programming language.
What about AI?
I don't believe anybody really knows what's going to happen with AI
and programming in the medium and long term. In my experience, at
present, AI has trouble writing a complete program, even if you
specify the requirements very clearly. But it can do a decent job,
getting a lot of it right, and it's getting better every month
(or every week, or every day, it often feels like).
In my experience, at present, it is necessary for a programmer
to review, understand, and fix up much of the code that AI writes.
Further, in my experience AI does well when writing short
programs from scratch, or inserting lines inside a function.
But when it comes to making global modifications
to a large existing program, it's hit-or-miss, and it will
often get things wrong, sometimes messing up unrelated parts of
the program that are not related to the requested change.
Here is how I currently use AI in programming, as of
July 2025:
-
I let Copilot make completion suggestions as I type in vscode.
It often does an uncanny job of knowing what I'm about to type.
It's also often way off, but it's not very distracting to just
ignore those incorrect guesses.
-
I sometimes ask AI to write a small bit of code, like a loop,
where I'm worried about making an off-by-one error, or some
other small error. It's easy for me to review this code
to make sure it's right.
-
I sometimes ask AI to write a small bit of code because I can't
fully remember how a certain API works. For instance, I recently
had a need to edit an SVG element by hand, and there are a lot of details
I don't remember. So I asked Copilot to write some lines. I find
this is an efficient way to learn.
-
I copy and paste error output, such as a confusing linker error
or an error in the nginx or systemd logs. I have been amazed by
how well the AI diagnoses the problem and identifies the
likely cause.
-
I often have conversations with AI about larger issues, such as the
discussion above about whether it's important to use a web framework
for small website projects.
The AI might bring up points I hadn't considered. Or I might end
up "convincing" the AI of my point of view. In either case, I
really appreciate this use case of AI, because such nuanced
discussions can be hard to find by googling.
Of course, the big question for someone learning to code now, is whether
the rapid improvements in AI make it pointless. If AI can now program better
than an entry-level programmer, and soon better than most intermediate
or senior level programmers, why learn to program? I don't have a
definitive answer but my thoughts are:
-
It might turn out that AI reaches a sort of asymptote, where it
gets better at many aspects of coding, but can never really
be trusted to work on large code bases because there's always
the worry that it will make unpredictable mistakes.
-
Even if AI gets really good and trustworthy at coding, it is hard
to predict exactly what roles will be left for people, but there
are likely to be a number of such roles. For instance, perhaps
the AI is good at the details of coding but not good at the
overall planning of a program such that it meets user requirements.
-
Even if AI gets really good and trustworthy at coding,
there will always be work related to monitoring the AIs, and
joining them together to handle the specifics of all the
existing code bases. There will always be unpredictable
problems and a human who understands code will be needed to
diagnose and fix them.
-
Even if AI gets really good and trustworthy at coding, studying
programming is still very valuable because it teaches you to
think logically, to break problems down and solve them
incrementally, and to think about resources and the expense
(in terms of time and space) that are required. Also, programming
is a form of writing, and it is always good to improve your
writing skills. It has been said that programs exist, first, as
a way for people to communicate about how things can be done,
and only incidentally as instructions for a computer to execute.
About Michael
Hi, I'm Michael Katz. I live in Seattle and spend
a lot of time in Romania. (I know many programming
languages but have been struggling with
Romanian. I thought it might be about the same difficulty as
Spanish, but I was very wrong.)
I have been programming since the early 1980s.
I have worked for many years for the emergency response division of
NOAA (part of the U.S. Department of Commerce) on
emergency response software for hazardous chemical
and oil spills.
In the past I worked for a few
different game companies, including Sierra Online
(later Sierra Entertainment). I have worked on a few
medical devices. I worked for a year at Amazon. I have
passed many technical programming interviews, and
have been a hiring manager myself.
I have also done
a lot of programming projects on the side, including
a number of games for iOS and Android, and more
recently some games and SaaS projects for the web.
I have a
bachelor's degree in computer science from Brandeis
University, and a master's degree in math, science,
and technology education from UC Berkeley. I have
done a lot of teaching and tutoring in both
programming and math, for both kids and adults.