CSC148H1/Unit 1
Unit 1 · Introduction to Computer Science

Abstract Data Types

The idea the whole course rests on: separate what a structure does from how it does it. Say what operations exist and what they promise, and leave the implementation free to change without breaking anyone.

By the end you can
  • Distinguish an abstract data type from a data structure.
  • Write a specification a client can rely on.
  • Say why the same ADT admits several implementations.
  • Use preconditions to move responsibility deliberately.
Why it matters & where it comes from
Why it exists

Without the separation, every user of your code depends on its internals, and you can never change them. With it, you can replace the implementation entirely and nothing that uses it needs to know.

In the real world

Every library API is an ADT. Python's list promises indexing and appending without telling you it is a dynamic array; the interpreter has changed how it works several times without breaking a single program.

Story & open questions

Barbara Liskov formalised abstract data types in the 1970s and won the Turing Award for it. Her rule, that a subtype must be usable wherever the supertype is expected, is now called the Liskov substitution principle and returns in Unit 10.

ADT versus data structure

Definition
An abstract data type is a set of values plus the operations on them, described by what they do. It says nothing about storage.
A data structure is a concrete way of storing the data so those operations can be carried out.

One ADT, many possible structures. "Stack" is an ADT; a Python list and a linked list are two structures that implement it.

Example The same ADT, two structures

A Stack promises: put an item on top, take the top item off, ask whether it is empty. That is the whole contract.

python
# implementation A: a Python list, top at the end
class Stack:
    def __init__(self) -> None:
        self._items = []

    def push(self, item) -> None:
        self._items.append(item)

    def pop(self):
        return self._items.pop()

# implementation B: a Python list, top at the front
class Stack:
    def __init__(self) -> None:
        self._items = []

    def push(self, item) -> None:
        self._items.insert(0, item)

    def pop(self):
        return self._items.pop(0)
Both satisfy the same contract. Client code cannot tell them apart by behaviour.
They differ enormously in cost: A is $O(1)$ per operation, B is $O(n)$ because inserting at the front shifts everything. Correctness is identical; performance is not.
Rule The underscore is a promise you make to yourself

A leading underscore, as in _items, marks an attribute as internal. Python does not enforce it — client code can reach in. The convention says: if you touch this and it breaks later, that is your fault, not the implementer's.

Writing a specification

Rule What a complete docstring carries
Type annotations on parameters and return value.
A one-line summary beginning with "Return" or naming the effect.
Preconditions — what the caller must guarantee.
Examples in doctest form.

In CSC148H1 this is marked, so it is worth getting into the habit immediately.

Example A specification worth relying on
python
def pop(self):
    """Remove and return the item most recently pushed onto this stack.

    Preconditions: this stack is not empty.

    >>> s = Stack()
    >>> s.push(1)
    >>> s.push(2)
    >>> s.pop()
    2
    """
    return self._items.pop()
The summary says what, not how. Nothing here mentions a list, so the implementation stays free.
The precondition moves responsibility to the caller. Without it, the method would owe an answer for the empty case — and would need to say what that answer is.
Rule Preconditions are a design decision

Two defensible choices for popping an empty stack:

Precondition: "this stack is not empty". Fast, and the caller must check. Undefined behaviour if they do not.
Raise an exception: defined behaviour for every input, at the cost of a check on every call.

Either is fine. What is not fine is leaving it unsaid, so callers guess and half of them guess wrong.

Why the separation pays

Example Swapping the implementation

Suppose client code is written against the Stack contract:

python
def reverse(items: list) -> list:
    """Return a new list with the items of items in reverse order.

    >>> reverse([1, 2, 3])
    [3, 2, 1]
    """
    s = Stack()
    for item in items:
        s.push(item)
    return [s.pop() for _ in range(len(items))]
This works with implementation A or B, unchanged. It never mentions _items.
Replace the Stack with a linked-list version in Unit 4 and this function still works. That is the entire payoff of programming to a contract.
Reaching past the interface

Writing s._items.append(x) instead of s.push(x) couples your code to the implementation. It works today and breaks the moment the structure changes — and the error will surface far from the line that caused it.

Common pitfalls

• Describing how in a docstring. The client does not care and should not depend on it.
• Accessing another object's underscore attributes.
• Omitting preconditions and leaving edge behaviour undefined.
• Confusing the ADT with one of its implementations. A stack is not "a list with append and pop".
• Writing the code before the specification. The specification is what tells you when the code is finished.

Practice

Write out each answer before opening it. Original exercises written for this guide in the style of the course's weekly work.

Practice 1 · Which is which

Is "queue" an ADT or a data structure?

Answer
An ADT. It specifies first-in-first-out behaviour without saying how the items are stored. A linked list or a Python list could implement it.
Practice 2 · Cost difference

Both stack implementations above are correct. Why prefer A?

Answer
A is $O(1)$ per operation; B is $O(n)$ because inserting and removing at the front shifts every element. Same contract, very different performance.
Practice 3 · Write a precondition

Specify a first() method returning the front of a queue.

Solution
python
def first(self):
    """Return the item at the front of this queue, without removing it.

    Preconditions: this queue is not empty.

    >>> q = Queue()
    >>> q.enqueue(5)
    >>> q.first()
    5
    """
Practice 4 · Leaky abstraction

What is wrong with this docstring?

python
def push(self, item) -> None:
    """Append item to the end of self._items."""
Answer
It describes the implementation, not the contract. A client reading it might rely on the list, and the docstring becomes wrong the moment the structure changes. Say "Add item to the top of this stack".
Practice 5 · Two designs

Give two reasonable behaviours for dequeuing an empty queue.

Answer
Declare a precondition that the queue is non-empty, or raise an exception. Both are defensible; leaving it unspecified is not.
Practice 6 · Client independence

Why can reverse above survive an implementation change?

Answer
It uses only push, pop and the constructor — the public contract. It never touches internals, so internals are free to change.
Practice 7 · Specify a Set

Write the operation list for a Set ADT.

Solution
Add an item, remove an item, test membership, report size. Notably absent: any notion of order or position — including those would over-specify the ADT and rule out hash implementations.
Practice 8 · When to break the rule

Is it ever right to access another object's private attribute?

Answer
Inside the same class, yes — a method may reach into another instance of its own type, which is how __eq__ works. Across classes, no. The boundary is the class, not the object.

Expanded topic checklist

Course coverage. This unit is part of the complete CSC148H1 topic map. It should cover the following items before you move on:

  • ADT versus structure. What each term means and why one ADT admits many structures.
  • Specifications. Type annotations, summary, preconditions, examples.
  • Information hiding. The underscore convention and what it does and does not enforce.
  • Design judgment. Choosing preconditions versus exceptions deliberately.
Textbook/source alignment

The calendar names "abstract data types" and "specifications" among the first topics. The course runs on its own notes; check Quercus for the required docstring format, which is marked on every assignment.

Core idea map

The contract is what clients depend on; the implementation is what you stay free to change. Every structure in this course is introduced as a contract first and code second.

Study routine. Before writing any method body, write its docstring in full. If you cannot state the contract clearly, the design is not settled and the code will not save it.

Deep practice extensions

Use these after the main practice set. They push on definitions and edge cases rather than routine calculation.

Extension 1 · The distinction

State the difference between an ADT and a data structure.

Guide
The ADT is the contract: operations and their behaviour. The data structure is one concrete way of meeting it.
Extension 2 · Underscore

Does Python enforce privacy?

Guide
No. The leading underscore is a convention. It signals intent and shifts blame, but nothing blocks access.
Extension 3 · Precondition purpose

What does a precondition do?

Guide
Moves responsibility to the caller. Behaviour is undefined if it is violated, which is why it must be stated.
Extension 4 · Cost versus contract

Can two correct implementations differ in complexity?

Guide
Yes, dramatically. The contract fixes behaviour, not cost, which is why choosing an implementation is a separate decision.