# An Intro to CS course in C++ for CS majors: laying a solid low-level foundation or simple tools and algorithmic thinking?

Consider the following two competing incentives in teaching a first Intro to CS course for CS majors. In my case, the course must use C++ -- that's a given.

The first incentive is that the course needs to provide a solid foundation, which includes understanding how things work under the hood and implies teaching pointer, raw array and string, dynamic allocation etc.

The second incentive is the focus on developing algorithmic thinking and taste for elegant code. This incentive seems to be served best by teaching the smallest and the simplest-to-use possible subset of the language sufficient to solve algorithmic problems, gradually getting to the level of problems used in informatics olympiads for high school. So, for example, std::vector would be taught instead of raw array and pointers would not be mentioned. This would probably also mean excluding the need to handle errors in the exercises, so as to not spoil the taste of writing short and elegant code.

Which approach is preferred? Or maybe a third approach?

Clarifications following the reply by @Ben I.

I am presenting this as a choice between two approaches, because I believe that the two approaches are in conflict. Low-level features of the language are complicated. To help students understand them, one needs to give a lot of technical examples and exercises. This totally takes the focus away from algorithmic thinking.

Also, I am writing under the assumption that, once I am convinced about the correct way of teaching, I can try to convince my colleagues to consider changes in the follow-up courses as well. Right now the curriculum is built on the first approach. The big picture that I see is that there are many CS graduates who know programming languages, but few who are good programmers. Look at the questions at leetcode.com. Almost all of them are about algorithmic thinking, because employers are looking for good programmers.

• You keep saying "2 approaches". But you have not listed any approaches. You have given 2 incentives. – ctrl-alt-delor Jan 11 at 11:04
• You are setting us an Alexander-pattern: Given theses 2 seemingly conflicting forces, you can do this. Unfortunately, I don't think there is a good pattern at this level. The pattern would be: first teach to program, then teach to code, then (maybe) teach low-level stuff. – ctrl-alt-delor Jan 11 at 11:05

You wrote:

The first incentive is that the course needs to provide a solid foundation, which includes understanding how things work under the hood and implies teaching pointer, raw array and string, dynamic allocation etc.

This is not the only definition of a "solid foundation". I would suggest that your second approach is also a "solid foundation", but towards a different goal.

You could make a wonderful course out of low-level computer workings, and your students could learn a tremendous amount. Your "second incentive" is higher-level, and you could make a wonderful course out of this as well.

In truth, I am not sure that your goals are entirely opposed to one another. You can teach high-level thinking and still mention what is going on under the hood, and you can teach good coding style in a low-level language. No matter which basic approach you take, you really should make an effort to help students see at least a bit of the other side.

But in answer to your question about a basic initial approach, I might suggest the following: we often think about introductory courses as preparing us for our long-term futures, but I would argue that on a far more practical level, course 101 lays the foundation for 102, 103, 201, 202, 303, and so forth. You are setting the stage for future learning. Therefore, the best (introductory) course is the one that helps the students get the most learning out of their later, more advanced courses. After all, those courses are where the rubber will really meet the road.

So, what will set them up for success as they proceed through your entire program? Have a few conversations with your colleagues, and take a careful look at your program's course catalog. The answer may become fairly obvious.

• Thank you. Following your reply, I added a clarification to the question. – AlwaysLearning Jan 9 at 8:26

Consider that computers (the hardware) and programs (the software) are designed and built as a layered system. Each layer defines a complete and consistent model of computing that can be used without any reference to lower levels - except in the implementation. You can program effectively in, say, Scheme, without reference to silicon or to registers or to physical memory, or to assembler language, or anything beyond what is defined in Scheme itself. Each layer is complete unto itself and consistent within itself. Lower levels are for compiler builders, not for programmers working in a particular language.

Consider that most programmers don't ever build OS kernels or device drivers except possibly in a specialized course. Most spend their efforts solving problems useful to users.

Consider that you can teach a given level without reference to any other level. A variable is a reference to a value. The value is defined within a range. The variable might be able to later reference a different value. The values can be combined to create other values using well defined and consistent rules. The matching of a variable to a memory location mixes levels in a way that is essential to a compiler builder, but not to an application programmer.

Consider that C and low level C++ is a minefield for beginners with many places to go wrong and get lost and frustrated. Once mastered, they are a beautiful thing for low level programming, but mastery comes slowly unless you have an overall picture of what you really want to attain. I could once program some beautiful things in C++ but felt the language and tools were fighting me every step of the way. I didn't actually need the low level stuff (even though I often built compilers) so gave it up.

Consider that C++ has been remaking itself for 35 years into something at a higher level. It has, in fact, been moving away from its roots as "a better C". Even more so than C as a replacement assembler language for early DEC machines.

If I were going to recommend using C++ for beginners, I would recommend using the latest version (17) and finding a book that emphasizes that. Use the modern libraries exclusively.

Teach students to program at a higher level and stay consistent within that level. That is why the level was created in the first place. Once they "know what a program is" they can then branch out to either lower or higher levels as needed. But mixing levels doesn't do them any favors, since it isn't needed and just adds complicaiton.

We don't, in fact, have to force students to recapitulate the entire history of computing before they can begin to solve the problems that need solving today. Sadly, too many people think that such a recapitulation is necessary. The reason for this is that they learned computing over those 35 years and so think back on how they first learned. But the ideas they learned way back then are now pretty much obsolete.

Even the "machine model" most people might use to illustrate how a program is executed (memory transfer) is obsolete. Hardware doesn't look like that anymore and the OS makes it look even less like that. A CPU and memory. A few general registers. A monolithic memory (RAM). Nope. Now it's multilevel cache, separate data and program cache, paged memory management, specialized registers, etc.

Save the complicated multi-level stuff for the compiler and OS courses. By then they should know how to program.

I'll note that Bjarne Stroustrup, who created C++ once said something like: “C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.”

Or, for fun: http://www.softpanorama.org/Lang/Cpp_rama/humor.shtml (which has an alternate quote).

A problem with teaching a high level language, however, is that the instructor probably already knows low-level languages and is far too likely to "explain" things in terms of a mapping. But the mapping may be to something that the students don't, themselves, already understand unless they, too, have been made to do the recapitulation of programming history.

So, you need to think about how to answer questions within the (complete and consistent) level defined by the language. And you need to be prepared.

• Thank you very much for a very instructive answer. It sounds that you are against the first approach per se, i.e. without relation to it taking away from my second incentive. Could you please say a few words about the latter and about the big picture that I described in the clarification at the bottom of my question? – AlwaysLearning Jan 9 at 15:37
• I’ll just note that often the best programmers think one level above and one level below where they are currently programming; so a scheme programmer should be at least somewhat concerned with the system his program fits into and the system his program runs on. There is no pure isolation—and your terminal description of the complex machinery at work is crucial sensitive programs. It would be better if more parts of our education got us to think on several levels and move between them fluidly. – D. Ben Knoble Jan 12 at 5:14
• @D.BenKnoble, I'm not sure that is true about how I think when I program. I doubt it, actually. And it isn't something to teach in the first course in any case. – Buffy Jan 12 at 12:37
• This answer sounds me like it is saying that we can can teach people music theory and they can then write symphonies without ever having played an instrument. I am pretty sure that Mozart and Beethoven had at least tried most of the instruments in the orchestra, and they were definitely proficient on piano. But they had to know that a note on the piano does not 'sustain', an oboe could play a note for as long as you could blow one breath, and you could sustain a note on a violin all day long if you wanted to. Programmers, if they are going to write 'symphonies', need hands on experience. – Scott Rowe Jan 13 at 2:02
• @ScottRowe, I don't think I suggested that you don't need hands on experience. You need a lot. I have no idea where this comment comes from. – Buffy Jan 13 at 11:46

Don't do it. Learning C++ is a MASSIVE undertaking. C++ is many many times bigger than C. Doing this at the same time as learning to program, will give sub-optimal results. I have tried to hire C++ programmers in the past. Almost all of the candidates could not even program: did not understand the most basic concepts. We had to hire non-C++ programmers, and cross train. (it is quicker to learn to program, and then learn C++). For your goals, it will be better to teach two languages: A good beginner language, and C. (I had not read your last 2 paragraphs when I wrote this)

# With respect of solid foundation:

I think this is often miss-understood. The concept of abstract/high-level vs low-level in computer-science/programming, is usually the complete opposite to that in teaching theory.

High-level programming, is less abstract from what the student already knows. So therefore should be the foundation. Think about how many car drivers there are, and how many of them can even check their windscreen-wash-level, let alone make a repair.

Therefore start high and conditionally work your way down (depending on specialist course).

• What exactly did you refer to by it in "don't do it"? As the question states, that the Intro to CS course is in C++ is a given. – AlwaysLearning Jan 11 at 16:54
• @AlwaysLearning Sorry to hear that. I know what I would do, but I can't tell you what to do. – ctrl-alt-delor Jan 11 at 21:59

I am presenting this as a choice between two approaches, because I believe that the two approaches are in conflict. Low-level features of the language are complicated. To help students understand them, one needs to give a lot of technical examples and exercises. This totally takes the focus away from algorithmic thinking.

I would challenge that frame to say that yes, they're in conflict in the sense that time spent on the one is time not spent on the other. But what you may actually need is something else: a well-rounded basis in C++.

My original introduction to C++ spent time both on low-level aspects like pointers, as well as abstract ideas like sorting. Then in computer architecture class, we dove into things like hardware-aware programming, and in compiler construction into how language constructs are translated into machine instructions. Low-level stuff. In algorithmics and data structures courses on the other hand, we focused on the abstract programming ideas, building up towards higher level technique.

All of these follow-up courses depended on C++, but both of the two "opposing" axes you bring up turned out to be necessary.

• By algorithmic thinking, I don't mean teaching algorithms like searching and sorting. I mean the students "breaking their heads" trying to solve algorithmically interesting questions. – AlwaysLearning Jan 11 at 16:49
• What's the purpose of this class? Is it to teach people the basics of programming or enough C++ so that they can use it in the other courses of the curriculum? Then high-flying algorithmic thinking isn't really on-topic. "Intro to CS" is rather vague as a statement of what you're supposed to learn in the course. But to give good advice on its intended content, we need to know what it's role in the overall curriculum is. – ObscureOwl Jan 12 at 4:43
• The purpose is to give an introduction to Computer Science whatever that entails. It seems that the person who developed the original curriculum of the course did not think that it should entail developing algorithmic thinking. I would like to try and challenge that. If I can convince my colleagues of the necessity of the change, I believe the follow-up courses (the next of which is called "Seminar in C++") will be able to compensate for the technical side. On the other hand, once the students get a bad taste in the first course, it is difficult to fix later. – AlwaysLearning Jan 12 at 8:17