Computing Curricula 2001
Computer Science Volume


Chapter 7
Introductory Courses

This chapter looks at the introductory phase of the undergraduate curriculum, when students receive their first college-level exposure to computer science. Section 7.1 outlines our overall philosophy concerning the introductory curriculum. Sections 7.2, 7.3, and 7.4 then look at three topics that are central to the design of introductory courses: the role of programming, the length of the introductory sequence, and strategies for integrating discrete mathematics. Section 7.5 goes on to enumerate the set of concepts, knowledge, and skills that we believe should be part of an ideal introductory sequence. Finally, section 7.6 enumerates a set of six introductory strategies that have proven successful in practice. This section also provides critiques of each approach as an aid to help faculty make informed decisions about which of the alternatives best address the needs of their students, their department, their institution, and their community.

7.1 Overall philosophy

Throughout the history of computer science education, the structure of the introductory computer science course has been the subject of intense debate. Many strategies have been proposed over the years, most of which have strong proponents and equally strong detractors. Like the problem of selecting an implementation language, recommending a strategy for the introductory year of a computer science curriculum all too often takes on the character of a religious war that generates far more heat than light.

In the interest of promoting peace among the warring factions, the CC2001 Task Force has chosen not to recommend any single approach. The truth is that no ideal strategy has yet been found, and that every approach has strengths and weaknesses. Given the current state of the art in this area, we are convinced that no one-size-fits-all approach will succeed at all institutions. Because introductory programs differ so dramatically in their goals, structure, resources, and intended audience, we need a range of strategies that have been validated by practice. Moreover, we must encourage institutions and individual faculty members to continue experimentation in this area. Given a field that changes as rapidly as computer science, pedagogical innovation is necessary for continued success.

7.2 Where does programming fit in the introductory curriculum

One of the most hotly debated questions in computer science education is the role of programming in the introductory curriculum. Throughout the history of the discipline, most introductory computer science courses have focused primarily on the development of programming skills. The adoption of a programming-first introduction arises from a number of practical and historical factors, including the following:

The programming-first approach, however, has several shortcomings. The most commonly cited objections to this approach are the following:

Despite these shortcomings, however, the programming-first model has proven to be extraordinarily durable. Even though the Computing Curricula 1991 report argued strongly for a broader introduction to the discipline, the majority of institutions continue to focus on programming in their introductory sequence. It is important to recognize that the programming-first model has some strengths that have led to its longevity. Of these, the most important are the following:

The members of the CC2001 Task Force believe that the programming-first model is likely to remain dominant for the foreseeable future. It is therefore incumbent on the task force to provide guidance as to how to make that structure work. In addition, we acknowledge that there appear to be serious problems inherent in that approach. To date, no adequate resolution has emerged. Thus, it is imperative that we encourage continued innovation and experimentation with alternative models aimed at addressing these problems. Alternative approaches that seek to challenge the dominance of the programming-first model, however, will have to take into account the pragmatic demands for applicable computing skills.

In section 7.6, we offer three implementations of a programming-first model and three that adopt an alternative paradigm. The programming-first implementations are an imperative-first approach that uses the traditional imperative paradigm, an objects-first approach that emphasizes early use of objects and object-oriented design, and a functional-first approach that introduces algorithmic concepts in a language with a simple functional syntax, such as Scheme. In each case, we have sought to identify curricular models that minimize the weaknesses of the programming-first approach by focusing on algorithmic and problem-solving concepts rather than the vagaries of language syntax. The three alternative models are a breadth-first approach that begins with a general overview of the discipline, an algorithms-first strategy that focuses on algorithms over syntax, and a hardware-first model that begins with circuits and then builds up through increasingly sophisticated layers in the abstract machine hierarchy.

7.3 The length of the introductory sequence

Although the philosophy and structure of introductory courses have varied widely over the years, one aspect of the computer science curriculum has remained surprisingly constant: the length of the introductory sequence. For several decades, the vast majority of institutions have used a two-course sequence to introduce students to computer science. In the computer science education community, these two courses are generally known as CS1 and CS2, following the lead of Curriculum '78 [ACM78]. While the content of these courses has evolved over time in response to changes in technology and pedagogical approach, the length of the sequence has remained the same.

We believe the time is right to question this two-course assumption. The number and complexity of topics that entering students must understand have increased substantially, just as the problems we ask them to solve and the tools they must use have become more sophisticated. An increasing number of institutions are finding that a two-course sequence is no longer sufficient to cover the fundamental concepts of programming, particularly when those same courses seek to offer a broader vision of the field. Expanding the introductory sequence to three courses makes it far easier to cover the growing body of knowledge in a way that gives students adequate time to assimilate the material.

The CC2001 Task Force strongly endorses the concept of moving to a three-course introductory sequence and believes that this option will prove optimal for a relatively wide range of institutions. At the same time, the three-course approach will not be right for everyone. The fact that the traditional two-course approach fits into a single year of study at semester-based institutions often makes it easier to fit the introductory material into the whole of the curriculum without interfering with the scheduling of sophomore-level courses. Similarly, the task of assigning credit for courses taken at other institutions, including advanced placement programs in secondary schools, becomes more complicated if one institution follows a two-semester calendar while the other covers the introductory material in three.

To support both two- and three-course introductions, the CC2001 Task Force has developed both options for three of the introductory tracks -- imperative-first, objects-first, and breadth-first -- for which the three-course model seems to have the greatest advantages. Similar extensions could be developed for the other three approaches, but are not included in this report. For each of the tracks, the two- and three-course variants are distinguished using the course numbering system. The three-course sequences for each track use the numbers 101, 102, and 103; the two-course sequences use 111 and 112.

7.4 Integrating discrete mathematics into the introductory curriculum

As we discuss in Chapter 9, the CC2001 Task Force believes it is important for computer science students to study discrete mathematics early in their academic program, preferably in the first year. There are at least two workable strategies for accomplishing this goal:
  1. Require computer science students to take courses in discrete mathematics concurrently with the introductory sequence. The course descriptions in Appendix B include two implementations of a discrete mathematics course: a one-term course (CS115) that covers the bulk of the material in the Discrete Structures (DS) area of the body of knowledge in an intensive way, and a two-term sequence (CS105 and CS106) that covers the required material, together with some useful elective topics, at a slower pace that encourages greater thoroughness of coverage.
  2. Integrate at least part of the material on discrete mathematics directly into the introductory computer science sequence so that students can more easily appreciate how these mathematical tools apply in practical contexts. While it is certainly advantageous to adopt this approach for certain topics, it is important to ensure that students have sufficient exposure to discrete mathematics to provide the necessary mastery of the material. Given the size of the Discrete Structures (DS) area in the body of knowledge, it is impossible to incorporate all the required topics into the introductory sequence without adding an additional course to the introductory sequence. Typical implementations will therefore incorporate some material into the computer science sequence but retain a one-term course in discrete structures to complete the coverage. The three-course implementation of the breadth-first approach (CS101B/102B/103B) adopts this model of integrating the mathematical material directly into the introductory courses.

7.5 Expectations of the introductory curriculum

Despite the ongoing debates over pedagogical approaches, there are many values that virtually all advocates of computer science education share. In this section, we outline what we believe constitutes a general consensus about a minimal set of goals for an introductory curriculum. Each individual strategy articulated in section 7.6 seeks to accomplish much more than what we describe here, but each will cover a common set of topics that can serve as a base for intermediate course structures.

In today's world, computers are ubiquitous. Because of the importance of computer systems and the wide applicability of computer-based skills, introductory computer science experience should certainly expose students to the design, construction, and application of computer systems and offer them training in skills that have demonstrated utility. At the same time, introductory computer science courses must also introduce students to some of the central intellectual aspects of the discipline. When we view computer science as a discipline, it is important to look beyond its popular conception as a tool to consider its conceptual foundations. Upon what principles does it stand? What new concepts does it bring to the realm of knowledge? What kinds of questions do computer scientists ask? What modes of thought and mental disciplines do they bring to bear on problems?

We believe it is possible to develop an introductory computer science experience that accomplishes each of the following goals:

To this end, Figure 7-1 presents a set of concepts, knowledge, and skills that we believe should be a part of each introductory curriculum. While the order of presentation and level of emphasis will vary among individual computer science programs, we expect that all introductory programs will seek to meet these goals. Figure 7-2 expresses similar guidelines in terms of units and topics from the body of knowledge introduced in Chapter 5.

Figure 7-1. Concepts covered in the introductory curriculum
Algorithmic thinking
    Concept Description Associated activities
    Algorithmic computation Algorithms as models of computational processes; examples of important algorithms Read and explain algorithms; reason about algorithmic correctness; use, apply, and adapt standard algorithms; write algorithms
    Algorithmic efficiency and resource usage Simple analysis of algorithmic complexity; evaluation of tradeoff considerations; techniques for estimation and measurement Estimate time and space usage; conduct laboratory experiments to evaluate algorithmic efficiency

Programming fundamentals

    Concept Description Associated activities
    Data models Standard structures for representing data; abstract (described by a model) and concrete (described by an implementation) descriptions Read and explain values of program objects; create, implement, use, and modify programs that manipulate standard data structures
    Control structures Effects of applying operations to program objects; what an operation does (described by a model); how an operation does it (described by an implementation) Read and explain the effects of operations; implement and describe operations; construct programs to implement a range of standard algorithms
    Order of execution Standard control structures: sequence, selection, iteration; function calls and parameter passing Make appropriate use of control structures in the design of algorithms and then implement those structures in executable programs
    Encapsulation Indivisible bundling of related entities; client view based on abstraction and information-hiding; implementer view based on internal detail Use existing encapsulated components in programs; design, implement, and document encapsulated components
    Relationships among encapsulated components The role of interfaces in mediating information exchange; responsibilities of encapsulated components to their clients; the value of inheritance Explain and make use of inheritance and interface relationships; incorporate inheritance and interfaces into the design and implementation of programs
    Testing and debugging The importance of testing; debugging strategies Design effective tests; identify and correct coding and logic errors

Computing environments

    Concept Description Associated activities
    Layers of abstraction Computer systems as a hierarchy of virtual machines Describe the roles of the various layers in the virtual machine hierarchy
    Programming languages and paradigms Role of programming languages; the translation process; the existence of multiple programming paradigms Outline the program translation process; identify at least two programming paradigms and describe their differences
    Basic hardware and data representation Rudiments of machine organization; machine-level representation of data Explain basic machine structure; show how different kinds of information can be represented using bits
    Tools Compilers, editors, debuggers, and other components of programming environments Use tools successfully to develop software

Applications Web browsers, word processors, spreadsheets, data bases, e-mail systems Make effective use of standard computing applications

Figure 7-2. Units covered by all six of the introductory tracks
Units for which all topics must be covered:
  DS1. Functions, relations, and sets
  DS2. Basic logic
  DS4. Basics of counting
  DS6. Discrete probability
  PF1. Fundamental programming constructs
  PF4. Recursion
  PL1. Overview of programming languages
  PL2. Virtual machines
  PL4. Declarations and types
  PL5. Abstraction mechanisms
  SP1. History of computing

Units for which only a subset of the topics must be covered:
  DS3. Proof techniques: The structure of formal proofs; proof techniques: direct, counterexample, contraposition, contradiction; mathematical induction
  PF2. Algorithms and problem-solving: Problem-solving strategies; the role of algorithms in the problem-solving process; the concept and properties of algorithms; debugging strategies
  PF3. Fundamental data structures: Primitive types; arrays; records; strings and string processing; data representation in memory; static, stack, and heap allocation; runtime storage management; pointers and references; linked structures
  AL1. Basic algorithmic analysis: Big O notation; standard complexity classes; empirical measurements of performance; time and space tradeoffs in algorithms
  AL3. Fundamental computing algorithms: Simple numerical algorithms; sequential and binary search algorithms; quadratic and O(N log N) sorting algorithms; hashing; binary search trees
  AR1. Digital logic and digital systems: Logic gates; logic expressions
  PL6. Object-oriented programming: Object-oriented design; encapsulation and information-hiding; separation of behavior and implementation; classes, subclasses, and inheritance; polymorphism; class hierarchies
  SE1. Software design: Fundamental design concepts and principles; object-oriented analysis and design; design for reuse
  SE2. Using APIs: API programming; class browsers and related tools; programming by example; debugging in the API environment
  SE3. Software tools and environments: Programming environments; testing tools
  SE5. Software requirements and specifications: Importance of specification in the software process
  SE6. Software validation: Testing fundamentals; test case generation

7.6 Implementation strategies for introductory computer science

This section describes six implementations of the introductory curriculum that the CC2001 Task Force feels have achieved a level of success that allows them to serve as models of best practices. Determining whether an approach has been successful, however, is more difficult than it might appear. Albert Shanker, the former president of the American Federation of Teachers, wrote that "educational experiments are doomed to succeed," in part because the energy their creators bring to the experiment creates an excitement that encourages success. Given enough enthusiasm, almost any pedagogical approach will succeed as long as its proponents remain committed to that vision. The real test is whether the initial success can be sustained when the approach is adopted by others.

In choosing our set of models for the first year, we have insisted that each strategy be endorsed by people other than the creator who have used the approach successfully. By doing so, we hope to limit our attention to those strategies that already have a track record of successful adoption. The six models identified in the sections that follow have each met the criterion of having a successful track record when taught by faculty other than the original designer. At the same time, it is important to note that only the underlying approaches have been subjected to this level of validation and not the specific course designs included in Appendix B. In particular, we did not find existing models for the three-semester implementations we have proposed under the imperative-first, objects-first, and breadth-first approaches. The approaches themselves have proven successful in the more traditional two-semester packaging, and we believe that there is good reason to believe that the three-semester implementations will achieve similar levels of success.

7.6.1 Imperative-first

The imperative-first approach is the most traditional of the models we have included in this report. As noted in section 7.3, we have proposed two implementations of the imperative-first model, one that covers the material in three semesters and one that completes the presentation in two, as follows:

The two-semester model is the traditional implementation. CS111I offers an introduction to programming in an imperative style, using a structure similar to that in CS1 as defined in Curriculum '78 [ACM78, Koffman84]. CS112I then extends this base by presenting much of the material from the traditional CS2 course [Koffman85], but with an explicit focus on programming in the object-oriented paradigm. The three-semester implementation (CS101I/102I/103I) expands the coverage of most topics to ensure that students are able to master these fundamental concepts before moving on. These courses also offer space for additional topics that will give students a wider conception of the discipline.

It is important to note that first course in either sequence -- CS101I or CS111I -- may well use an object-oriented language for its programming examples and exercises. What distinguishes this approach from the objects-first model is the emphasis and ordering of the early topics. Even if it is taught using an object-oriented language, the first course focuses on the imperative aspects of that language: expressions, control structures, procedures and functions, and other central elements of the traditional procedural model. The techniques of object-oriented design are deferred to the follow-on course.

The imperative-first strategy is subject to the disadvantages -- as well as the advantages -- of any programming-first implementation, as outlined in section 7.2. In addition, adopting the imperative-first strategy means that students will get less exposure to the techniques of object-oriented programming than they would if the curriculum followed the objects-first model. Given the centrality of object-oriented programming in the curriculum requirements and the difficulty students have learning to program in an object-oriented style, the fact that the introduction of this material is delayed to the second course is clearly a weakness in this approach. At the same time, students do need exposure to the traditional imperative style of programming, which remains in widespread use and which is an integral part of any object-oriented languages. Opinion in the community is divided as to which model should be presented first. Some argue that students who have learned the imperative model first have more trouble adopting an object-oriented approach. Others counter that students who have grown used to working in an object-oriented language will chafe at the idea of learning to work without those features that makes object-oriented programming so powerful.

In any event, institutions adopting the imperative-first model will need to include additional coverage of object-oriented design principles at the intermediate level.

7.6.2 Objects-first

The objects-first model also focuses on programming, but emphasizes the principles of object-oriented programming and design from the very beginning. As with the imperative model, we propose both a two- and three-semester implementation in this report, as follows:

The first course in either sequence begins immediately with the notions of objects and inheritance, giving students early exposure to these ideas. After experimenting with these ideas in the context of simple interactive programs, the course then goes on to introduce more traditional control structures, but always in the context of an overarching focus on object-oriented design. The follow-on courses then go on to cover algorithms, fundamental data structures, and software engineering issues in more detail.

The principal advantage in the objects-first strategy is the early exposure to object-oriented programming, which has become increasingly important in both academia and industry. The December 2000 announcement by the College Board that they plan to introduce a more object-oriented approach in the Advanced Placement curriculum underscores the importance of this approach [AP2000]. At the same time, the objects-first model does not specifically address many of the disadvantages common to programming-first approaches, as described in section 7.2. In fact, the problems of the programming-first approach can be exacerbated in the objects-first model because many of the languages used for object-oriented programming in industry -- particularly C++, but to a certain extent Java as well -- are significantly more complex than classical languages. Unless instructors take special care to introduce the material in a way that limits this complexity, such details can easily overwhelm introductory students.

7.6.3 Functional-first

The functional-first style was pioneered at MIT in the 1980s [Abelson85] and is characterized by using a simple functional language, such as Scheme, in the first course. Compared to the other programming-first implementations, this approach has the following advantages:

There are, however, some dangers in this approach. The first is that students may react skeptically to learning a language that they see as outside of the mainstream. For students who are already committed to computer science, it is possible to overcome this objection by exploiting the expressive power of these languages and showing how much students can accomplish using these tools. For students who are taking an introductory computer science course to test the waters before jumping in, and particularly for students who see the course as a way to learn some practical programming skills, functional languages are a much harder sell. The second danger is that this approach typically requires students to think much more abstractly at an early stage than is true with more traditional programming languages. While forcing students to think in this way is certainly valuable and needs to be part of the curriculum at some point, placing it so early can discourage some students with less experience in that style of thought.

To cover the material that is essential in the first year, an introductory course that follows the functional-first strategy must be followed by an intensive course that covers object-oriented programming and design. The sample courses that implement this strategy are

7.6.4 Breadth-first

For many years, there has been concern in the computer science education community that the traditional "programming-oriented" introduction to computer science gives students a limited view of the discipline. Computer science, after all, is an ever-expanding field that includes many activities beyond programming. Courses that emphasize only this one aspect fail to let students experience the many other areas and styles of thought that are part of computer science as a whole.

To provide that more holistic view of the discipline, many computer science educators have argued for a "breadth-first" approach in which the first course considers a much broader range of topics. This approach was recommended strongly in both Computing Curricula 1991 and the earlier "Computing as a Discipline" report, which envisioned a curriculum in which "the first courses in computer science would not only introduce programming, algorithms, and data structures, but introduce material from all the other subdisciplines as well," making sure that "mathematics and other theory would be well integrated into the lectures at appropriate points" [Denning89].

Developing a successful breadth-first implementation, however, has proven to be difficult. In our surveys, the most common implementation of the breadth-first idea was to create an introductory "breadth-first" course that introduces the field to majors and nonmajors alike. Such a course gives students exposure to a range of interesting and important topics rather than plunging them immediately into the details of one specific area. Students who are interested in learning more about the field can then begin a "regular" one-year introductory sequence. Thus, most existing models involve the addition of a single breadth-first introductory course as the entry point into the discipline for all students. Students who complete such a course can then move on to any of the other introductory sequences with a much stronger perspective on the field.

The advantage of offering the breadth-first model as a lead-in to a more conventional programming sequence is that doing so gives students an immediate appreciation for the breadth of computer science, allowing them to more easily decide whether this is a field they do or do not wish to study in depth. The primary disadvantage of this approach, however, is that it adds one course to the size of the major and delays by a term the completion of the introductory sequence.

In our discussions, the CC2001 Task Force saw no reason why it would not be possible to create a successful breadth-first sequence, particularly if one abandons the view that the introductory sequence must be two semesters long. The basic idea is to take the material in the first-year courses -- the introductory programming sequence and the discrete mathematics courses -- and reassemble them in a way that provides early exposure to the breadth of the field. Unfortunately, we have not been able to identify any such models that meet our acceptance criterion of successful implementation by faculty other than the originator. We therefore have presented two separate implementations of a breadth-first approach:

Another approach to providing breadth in the curriculum is to offer a survey of the field after the completion of the introductory programming sequence. This "breadth-second" approach means that students begin with a programming-based introduction to make sure they have the necessary implementation skills but then have an early chance to appreciate the range of topics that are all part of computer science. While we feel that such an approach is worth pursuing as an experiment, we have not yet found models that meet our criterion for acceptance.

7.6.5 Algorithms-first

In this approach, the basic concepts of computer science are introduced using pseudocode instead of an executable language. By introducing students to basic algorithmic concepts and constructs apart from any particular executable language, this approach minimizes the preoccupation with syntactic detail that demands for successful program execution typically engender among students. Instead, it requires that students reason about and explain the algorithms they construct, tracing them by hand and mind. It permits students to work with a range of data and control structures without having to contend with the various peculiarities that popular programming languages inevitably introduce. Moreover, because students are freed from the need to execute their programs, this approach permits students to experience the range of such constructs more rapidly. Once students have a solid grasp of the algorithmic foundations and the range of data and control structures used in the pseudocode, they can then move on to a more conventional language, either partway through the first course or, at the latest, the beginning of the second course. Because students have experienced a wider range of both data and control structures early, their later progress through conventional programming work can occur more rapidly and class time can be more explicitly focused on issues of effective programming practices and systematic debugging skills.

By eliminating some of the time spent on syntax and the details of a particular programming environment, an introductory course that follows the algorithms-first approach can include additional theoretical topics, such as estimations of efficiency and the rudiments of computability. Doing so appears to be useful in two respects:

  1. For non-majors, it permits some access to the "science" of computer science.
  2. For computer science majors, it permits them to encounter appropriate aspects of theory from the very beginning of their course of study, minimizing the risk that they will later experience coursework in theory as an irrelevant curricular appendage.

At the same time, the algorithms-first approach has several critical weaknesses. For one thing, students at the introductory level want to experience the power and satisfaction that comes from making computers actually do something. Courses focused only on constructing algorithms in pseudocode frustrate this motivation and desire. It is therefore useful to combine the algorithms-first approach with laboratory-based exposure to modern application software that provides students with applied computing experience. This strategy helps students develop a practical skill set that may be of greater relevance to nonmajors than conventional programming. By synchronizing the laboratory-and-project agenda in software applications with the lecture-and-homework coverage of algorithms, students experience the relevance of, for example, data structures in the context of database work, control structures in the context of spreadsheet development, and high-level design in the context of web page creation.

Relying on pseudocode, however, also has the effect of freeing students from the need to demonstrate that their algorithms work in the context of a functioning implementation. While the process of getting a program to compile and execute correctly is sometimes frustrating, it is also a critical skill that students must master early in their education. The process of debugging in the pseudocode environment is quite different from the process of debugging an executable program. The former is characterized by desk-checking and reasoning about the algorithm; the latter is usually more of function of interpreting symptoms and learning to do the detective work associated with finding programming errors. Both skills are important, and it is difficult to assess how the algorithms-first approach affects the students' facility with the debugging process.

The final concern about the algorithms-first approach is that it requires substantial grading effort. While it is certainly inappropriate to assess introductory programming assignments solely on successful execution in a set of test cases, being able to make such tests helps graders identify algorithmic errors much more easily. Evaluating pseudocode for correctness is a much harder challenge that typically requires extensive use of course assistants.

In this report, the algorithms-first approach is illustrated by the following courses:

The first course focuses on algorithms and applications early, and then goes on to offer an introduction to object-oriented programming towards the end. The second course provides intensive coverage of object-oriented programming to ensure that students are up to speed on these techniques.

7.6.6 Hardware-first

The hardware-first approach teaches the basics of computer science beginning at the machine level and building up toward more abstract concepts. The basic philosophy behind this strategy is for students to learn about computing in a step-by-step fashion that requires as little mystification as possible. The syllabus begins with switching circuits, uses those to make simple registers and arithmetic units, and then embeds those in a simple von Neumann machine. Only after establishing the hardware foundation, does the course go on to consider programming in a higher-level language.

The courses that comprise this model are

The first course in the sequence covers the computer from the bottom up; the second uses that foundation to build up the programming skills of the students and to give them a solid introduction to object-oriented techniques.

Such an approach works well for students who prefer to understand the process of computation in all of its reductionist detail. It is less effective at encouraging students to see the holistic concepts beyond the mechanics of implementation. The hardware-first approach is also somewhat at odds with the growing centrality of software and the tendency of increasingly sophisticated virtual machines to separate the programming process from the underlying hardware. At the same time, we believe that such a course might be particularly viable in a computing engineering program, where early exposure to hardware issues is essential.


CC2001 Report
December 15, 2001