User-defined data types

A2 · 15 min

The built-in data types (INTEGER, REAL, CHAR, STRING, BOOLEAN, DATE) can each hold only one kind of value, but real problems are about things: students, seasons, flights, sets of options. A user-defined data type lets the programmer describe those things directly. In Paper 3 you are asked to explain why such types are needed, to classify them as composite or non-composite, and to write TYPE declarations in Cambridge pseudocode; in Paper 4 you implement the same ideas (records especially) in Python, Java or Visual Basic.

Why user-defined types are needed

A program that stores a student as five separate arrays (LastName[], FirstName[], DateOfBirth[], YearGroup[], FormGroup[]) works, but every array has to be kept in step by hand. Sort one array and forget another, and the data is silently corrupted. If instead there is one type, StudentRecord, that holds all five fields, the program can store one array of StudentRecord, and a student's data can never be split apart.

That is the general reason: the built-in types describe how data is stored, not what it means. A user-defined type is built from the built-in types (or from other user-defined types) so that the program's data matches the problem.

Definition

A user-defined data type is a data type that is derived from one or more existing data types and is defined by the programmer, for use in a particular program or problem.

The benefits that earn marks are:

  • It models the problem. Data that belongs together is stored together and referred to by one identifier.
  • It restricts values. An enumerated type Season can only hold the four seasons, so an invalid value such as "Smmer" cannot be stored.
  • It makes code readable and maintainable. ThisSeason ← Winter explains itself; ThisSeason ← 3 does not.
  • It can be reused. Once StudentRecord is defined, any number of variables, arrays and files can use it, and a change to the definition is made in one place.
  • It provides types the language lacks. Data structures that are not built into a language (records in Python, sets in some languages, linked lists everywhere) have to be constructed from what the language does provide.

Composite and non-composite types

Cambridge classifies every user-defined type in one of two ways.

Definition

A non-composite data type is a data type that does not reference any other data type. It holds a single value. The two you must know are enumerated and pointer types.

A composite data type is a collection of data that can consist of one or more data types, grouped under one identifier. Examples are record, set, class/object and array; the abstract data types (stack, queue, linked list, dictionary, binary tree) are also composite.

TypeComposite?What it holdsPseudocode keyword
EnumeratedNon-compositeOne value from a fixed, ordered listTYPE Name = (v1, v2, ...)
PointerNon-compositeA memory address of a value of a stated typeTYPE Name = ^DataType
RecordCompositeSeveral named fields, possibly of different typesTYPE ... ENDTYPE
SetCompositeAn unordered collection of distinct values of one typeTYPE Name = SET OF DataType
Class / objectCompositeAttributes plus the methods that act on themCLASS ... ENDCLASS
Watch out

A pointer is non-composite even though it "points at" another type. It holds a single value, an address. Students often call it composite because its declaration mentions another type; the mark scheme does not accept that.

Enumerated types

An enumerated data type is a non-composite type defined by listing every value it may take. The values are identifiers, not strings, so they are written without quotation marks.

TYPE Season = (Spring, Summer, Autumn, Winter)

DECLARE ThisSeason : Season
DECLARE NextSeason : Season

ThisSeason ← Autumn
NextSeason ← ThisSeason + 1     // NextSeason is now Winter
IF ThisSeason > Summer THEN
   OUTPUT "Second half of the year"
ENDIF

The values are ordered in the order they are listed. That is why ThisSeason + 1 and ThisSeason > Summer make sense: Spring < Summer < Autumn < Winter. Adding 1 to the last value (Winter + 1) is not defined, so a careful program checks for it.

In Python, the enum module provides enumerations. IntEnum keeps the ordering and arithmetic of the pseudocode version.

from enum import IntEnum

class Season(IntEnum):
    SPRING = 1
    SUMMER = 2
    AUTUMN = 3
    WINTER = 4

this_season = Season.AUTUMN
next_season = Season(this_season + 1)   # Season.WINTER
print(next_season.name)                 # WINTER
print(this_season > Season.SUMMER)      # True

for s in Season:                        # iterate in order
    print(s.value, s.name)

Pointer types

A pointer is a non-composite type whose value is the memory address of another data item. The declaration states what type of data lives at that address.

TYPE TIntPointer = ^INTEGER      // a pointer to an integer
DECLARE Number : INTEGER
DECLARE MyPointer : TIntPointer  // no caret when declaring the variable

Number ← 5
MyPointer ← ^Number              // ^Number is the ADDRESS of Number
MyPointer^ ← MyPointer^ + 3      // MyPointer^ is the VALUE at that address
OUTPUT Number                    // 8

The caret does two different jobs depending on where it stands:

Key result
ExpressionMeaning
TYPE TIntPointer = ^INTEGERdeclares a pointer type that points to an INTEGER
^Number (caret before a variable)the address of Number
MyPointer^ (caret after a pointer)the value stored at the address held in MyPointer (dereferencing)
MyPointer (no caret)the address itself

Pointers matter because they let a program build dynamic structures. A linked list node holds data and a pointer to the next node; a binary tree node holds two pointers. When you implement those structures in an array (as Cambridge questions usually do), the "pointer" becomes an integer array index, which is exactly the same idea: a value that tells you where to find the next item.

Python has no pointer type. Every Python variable already holds a reference to an object, so an object can be shared, but you cannot take or print an address. In Paper 4, pointers are almost always simulated with integer indices into a list.

Records

A record is a composite type made of a fixed number of named fields, which may be of different types.

TYPE StudentRecord
   DECLARE LastName : STRING
   DECLARE FirstName : STRING
   DECLARE DateOfBirth : DATE
   DECLARE YearGroup : INTEGER
   DECLARE FormGroup : CHAR
ENDTYPE

DECLARE Pupil1 : StudentRecord
DECLARE Form : ARRAY[1:30] OF StudentRecord

Pupil1.LastName ← "Johnson"
Pupil1.YearGroup ← 6
Form[1] ← Pupil1                 // whole records can be assigned
FOR Index ← 1 TO 30
   Form[Index].YearGroup ← Form[Index].YearGroup + 1
NEXT Index

Fields are reached with dot notation: Pupil1.LastName, Form[Index].YearGroup. Records are the foundation of file processing: a random-access file is usually a file of records of one type.

Python has no record keyword. The usual Paper 4 approach is a class with only a constructor; a dataclass is a tidy alternative.

class StudentRecord:
    def __init__(self, last_name="", first_name="", date_of_birth=None,
                 year_group=0, form_group=""):
        self.last_name = last_name
        self.first_name = first_name
        self.date_of_birth = date_of_birth
        self.year_group = year_group
        self.form_group = form_group

# an "array" of 30 empty records
form = [StudentRecord() for _ in range(30)]
form[0] = StudentRecord("Johnson", "Leroy", "02/01/2005", 6, "A")

for pupil in form:
    pupil.year_group += 1

print(form[0].last_name, form[0].year_group)   # Johnson 7
print(form[1].year_group)                      # 1
Watch out

form = [StudentRecord()] * 30 in Python creates one record referenced thirty times, so changing form[0] changes them all. Use a list comprehension, as above, so each element is a separate object.

In Java a record is a class with fields; in Visual Basic it is a Structure:

Structure StudentRecord
    Dim LastName As String
    Dim FirstName As String
    Dim DateOfBirth As Date
    Dim YearGroup As Integer
    Dim FormGroup As Char
End Structure

Sets

A set is a composite type holding an unordered collection of values of one type, with no duplicates. Cambridge pseudocode declares a set type and then defines a set of that type:

TYPE LetterSet = SET OF CHAR
DEFINE Vowels ('A', 'E', 'I', 'O', 'U') : LetterSet

The operations you should be able to describe are the mathematical ones:

OperationMeaningExample with A={1,2,3}A = \{1, 2, 3\}, B={2,3,4}B = \{2, 3, 4\}
Membershipis a value in the set?2 is in AA; 4 is not
Unioneverything in either setA∪B={1,2,3,4}A \cup B = \{1, 2, 3, 4\}
Intersectioneverything in both setsA∩B={2,3}A \cap B = \{2, 3\}
Differencein the first but not the secondA−B={1}A - B = \{1\}

Sets suit problems where you only ask "is it in or not?": which pizza toppings were chosen, which seats are booked, which letters have been guessed in a word game. Python's built-in set supports all of these.

vowels = {'A', 'E', 'I', 'O', 'U'}
word = "EXAMINATION"
letters = set(word)                 # duplicates removed automatically

print(sorted(letters & vowels))     # intersection: ['A', 'E', 'I', 'O']
print(sorted(letters - vowels))     # difference:   ['M', 'N', 'T', 'X']
print('U' in letters)               # membership:   False
print(len(letters | vowels))        # union size:   9

Classes and objects

A class is a composite type that bundles data (attributes) with the methods that operate on it; an object is an instance of a class. It goes beyond a record because the data can be made private and changed only through the class's own methods (encapsulation). Classes are covered fully in object-oriented programming; for this topic, know that a class is a composite user-defined type.

CLASS Pet
   PRIVATE Name : STRING
   PUBLIC PROCEDURE NEW(GivenName : STRING)
      Name ← GivenName
   ENDPROCEDURE
   PUBLIC FUNCTION GetName() RETURNS STRING
      RETURN Name
   ENDFUNCTION
ENDCLASS

Choosing a user-defined type

Choosing and designing a type for a given problem
  1. Ask what one item of the problem is. Several pieces of related data about one thing mean a record (or a class, if it also has behaviour or must protect its data).
  2. Ask whether a value comes from a small fixed list of named options. If so, use an enumerated type.
  3. Ask whether the program only needs to know membership of a collection, with no order and no repeats. If so, use a set.
  4. Ask whether one item must refer to another item stored elsewhere (next node, parent, child). If so, use a pointer (or an integer index acting as a pointer).
  5. Write the declaration in pseudocode, giving every field an identifier and an appropriate built-in type, and justify each choice in one phrase.
Declaring an enumerated type

A traffic light can be red, red-and-amber, green or amber.

(a) Write pseudocode to declare an enumerated type LightState for the four states, in the order they occur. (b) Write pseudocode to declare a variable Current of this type and move it to the next state, returning to the first state after the last.

Solution

(a)

TYPE LightState = (Red, RedAmber, Green, Amber)

(b)

DECLARE Current : LightState
Current ← Red
...
IF Current = Amber THEN
   Current ← Red
ELSE
   Current ← Current + 1
ENDIF

Marks are for: the TYPE keyword with the values in brackets and no quotes (they are values of the type, not strings); the declaration using the new type; and dealing with the last value separately, because Amber + 1 does not exist.

Designing a record and using an array of records

A library stores, for each book: an ISBN of 13 digits, a title, the number of copies owned, the date it was last borrowed and whether it is a reference-only book.

(a) Write a pseudocode declaration for a record type Book. (b) The library holds up to 500 books in an array Library. Write pseudocode to declare the array and to output the titles of all reference-only books with more than two copies.

Solution

(a)

TYPE Book
   DECLARE ISBN : STRING
   DECLARE Title : STRING
   DECLARE Copies : INTEGER
   DECLARE LastBorrowed : DATE
   DECLARE ReferenceOnly : BOOLEAN
ENDTYPE

The ISBN is a STRING, not an INTEGER: it is an identifier, never used in arithmetic, it may begin with zeros and 13 digits can exceed the range of a 32-bit integer.

(b)

DECLARE Library : ARRAY[1:500] OF Book
DECLARE Index : INTEGER

FOR Index ← 1 TO 500
   IF Library[Index].ReferenceOnly = TRUE AND Library[Index].Copies > 2 THEN
      OUTPUT Library[Index].Title
   ENDIF
NEXT Index

The same in Python, with sample data so it can be run:

class Book:
    def __init__(self, isbn, title, copies, last_borrowed, reference_only):
        self.isbn = isbn
        self.title = title
        self.copies = copies
        self.last_borrowed = last_borrowed
        self.reference_only = reference_only

library = [
    Book("9780000000011", "Atlas of the World", 3, "01/09/2026", True),
    Book("9780000000028", "Learning Python", 5, "14/09/2026", False),
    Book("9780000000035", "Oxford Dictionary", 4, "20/08/2026", True),
    Book("9780000000042", "Tide Tables", 1, "02/02/2026", True),
]

for book in library:
    if book.reference_only and book.copies > 2:
        print(book.title)
# Atlas of the World
# Oxford Dictionary
Tracing pointer code

Study this pseudocode.

TYPE TIntPointer = ^INTEGER
DECLARE A, B : INTEGER
DECLARE P, Q : TIntPointer

A ← 10
B ← 20
P ← ^A
Q ← ^B
P^ ← P^ + Q^
Q ← P
Q^ ← Q^ * 2
OUTPUT A, " ", B

Variable A is stored at address 3000 and B at address 3004. Complete a trace and state the output.

Solution
StatementABPQNotes
A ← 1010
B ← 201020
P ← ^A10203000P holds the address of A
Q ← ^B102030003004
P^ ← P^ + Q^302030003004value at 3000 becomes 10 + 20
Q ← P302030003000Q now also points at A
Q^ ← Q^ * 2602030003000value at 3000 doubled

Output: 60 20.

The trap is the line Q ← P. It copies the address, not the value, so from then on both pointers refer to A and B is never changed again.

Justifying a choice of types

A pizza ordering program must store, for each order: the size (small, medium or large), the toppings chosen from a menu of twelve, the customer's name and the time ordered. Identify an appropriate user-defined type for each of the size, the toppings and the order as a whole. Justify each choice and classify each as composite or non-composite.

Solution
  • Size: enumerated type, TYPE PizzaSize = (Small, Medium, Large). There is a small fixed list of named values, and the type prevents any other size being stored. It is ordered, so Size > Small can be used when pricing. Non-composite.
  • Toppings: set, TYPE ToppingSet = SET OF Topping (where Topping is itself an enumerated type of the twelve toppings). Each topping is either on the pizza or not, order does not matter and no topping is chosen twice, so membership is the only question asked. Composite.
  • Order: record containing Size : PizzaSize, Toppings : ToppingSet, CustomerName : STRING and TimeOrdered : DATE (or a STRING time). The four items belong to one order and must be kept together. Composite.

A full answer names the type, says what makes it suitable for this data, and classifies it. "A record is easier" earns nothing.

Linking records, enumerations and pointers

A railway timetable program stores trains in an array Trains[1:100]. Each train has a code (for example "IC205"), a status that is one of on time, delayed or cancelled, and the index of the next train on the same route (0 if it is the last). Write the type declarations, then write a pseudocode procedure ShowRoute(Start : INTEGER) that outputs the code of every train on a route, following the indices from Start.

Solution
TYPE TrainStatus = (OnTime, Delayed, Cancelled)

TYPE Train
   DECLARE Code : STRING
   DECLARE Status : TrainStatus
   DECLARE NextTrain : INTEGER     // index acting as a pointer; 0 = none
ENDTYPE

DECLARE Trains : ARRAY[1:100] OF Train

PROCEDURE ShowRoute(Start : INTEGER)
   DECLARE Current : INTEGER
   Current ← Start
   WHILE Current <> 0
      OUTPUT Trains[Current].Code
      Current ← Trains[Current].NextTrain
   ENDWHILE
ENDPROCEDURE

The integer field NextTrain does the job of a pointer: it tells the program where the next item is. This is exactly how a linked list is stored in an array, and the WHILE loop is a linked-list traversal.

The Python version, with three trains on one route:

from enum import Enum

class TrainStatus(Enum):
    ON_TIME = 1
    DELAYED = 2
    CANCELLED = 3

class Train:
    def __init__(self, code="", status=TrainStatus.ON_TIME, next_train=0):
        self.code = code
        self.status = status
        self.next_train = next_train

trains = [Train() for _ in range(101)]          # index 0 unused, as in [1:100]
trains[4] = Train("IC205", TrainStatus.ON_TIME, 9)
trains[9] = Train("IC211", TrainStatus.DELAYED, 2)
trains[2] = Train("IC230", TrainStatus.CANCELLED, 0)

def show_route(start):
    current = start
    while current != 0:
        print(trains[current].code, trains[current].status.name)
        current = trains[current].next_train

show_route(4)
# IC205 ON_TIME
# IC211 DELAYED
# IC230 CANCELLED
Exam tip
  • "Explain why user-defined data types are necessary" wants two or three of: no suitable built-in type exists; to model the data of the problem; to group related data under one identifier; to restrict values (enumerated); to make code clearer and easier to maintain. Tie each point to the scenario if one is given.
  • Classification questions are frequent: enumerated and pointer are non-composite; record, set, class and array are composite. State why: a non-composite type does not reference another data type; a composite type is a collection of items of one or more data types.
  • When writing declarations, copy the guide's layout exactly: TYPE ... ENDTYPE with DECLARE inside for a record; brackets with no quotes for an enumerated type; ^ before the type name for a pointer; SET OF then DEFINE for a set.
  • Choose field types sensibly and be ready to justify them: telephone numbers, ISBNs and codes with leading zeros are STRING; money is REAL; yes/no is BOOLEAN.
  • In pointer traces, keep the address and the value at the address in separate columns.
Summary
  • A user-defined type is defined by the programmer from existing types to model the data of a particular problem.
  • Non-composite types (enumerated, pointer) hold a single value and do not reference another type; composite types (record, set, class, array, ADTs) group several items.
  • Enumerated: TYPE Season = (Spring, Summer, Autumn, Winter); values are ordered identifiers, not strings.
  • Pointer: TYPE TIntPointer = ^INTEGER; ^X is the address of X; P^ is the value P points to.
  • Record: TYPE ... ENDTYPE with DECLARE fields; access with dot notation; the basis of files of records.
  • Set: TYPE LetterSet = SET OF CHAR then DEFINE; unordered, no duplicates; union, intersection, difference, membership.
  • Choose the type from the nature of the data, and justify it in terms of the scenario.

Practice questions

Question
  1. State the difference between a composite and a non-composite data type, and give two examples of each.

  2. Write pseudocode to declare an enumerated type Direction holding North, East, South and West, and a variable Facing of that type. Write a statement that turns Facing clockwise by one step, assuming Facing is not West.

  3. Explain why an enumerated type is more appropriate than a STRING for storing the day of the week.

  4. A hospital stores, for each patient, an ID such as "P00417", a name, a date of admission, a ward number from 1 to 40 and whether they are in intensive care. Write a pseudocode declaration of a record type Patient, and declare an array Ward able to hold 25 patients.

  5. Using your declaration from question 4, write pseudocode to count and output how many patients in Ward are in intensive care.

  6. State the output of this pseudocode and explain it.

    TYPE TIntPointer = ^INTEGER
    DECLARE X : INTEGER
    DECLARE Ptr : TIntPointer
    X ← 7
    Ptr ← ^X
    X ← X + 1
    OUTPUT Ptr^ * 2
  7. A word game records which letters a player has guessed. Explain why a set is an appropriate type, and describe the set operation that would find the letters of the hidden word that have not yet been guessed.

  8. Write a Python class to act as a record for a Car with fields registration (string), make (string), year (integer) and electric (Boolean). Write code to create a list of three cars and output the registration of every electric car built after 2020.

  9. A school timetable system must store lessons. Each lesson has a day (Monday to Friday), a period (1 to 6), a subject, a room and a set of the student groups that attend. Design suitable user-defined types for this data, writing every declaration in pseudocode and justifying each type.

  10. Explain, with reference to a linked list, why pointer data types are needed, and describe how a pointer can be represented when a linked list is stored in an array in a language without pointers.

Answers
  1. A non-composite type does not reference any other data type; it holds a single value. Examples: enumerated, pointer. A composite type is a collection of data items, which can be of one or more data types, under one identifier. Examples: record, set, class (also array, stack, queue).

```pseudocode
TYPE Direction = (North, East, South, West)
DECLARE Facing : Direction
Facing ← Facing + 1
```

`Facing + 1` works because enumerated values are ordered as listed; a full solution would set `Facing ← North` when `Facing = West`.

3. Only the seven valid values can be stored, so invalid data such as "Mnday" or "monday " is impossible and no validation is needed; the values are ordered, so comparisons (Day > Wednesday) and stepping to the next day work; and the code is clearer and uses less storage than a string.

```pseudocode
TYPE Patient
   DECLARE PatientID : STRING
   DECLARE Name : STRING
   DECLARE AdmissionDate : DATE
   DECLARE WardNumber : INTEGER
   DECLARE IntensiveCare : BOOLEAN
ENDTYPE

DECLARE Ward : ARRAY[1:25] OF Patient
```

The ID is a `STRING` because it contains a letter and leading zeros.

5.

```pseudocode
DECLARE Count, Index : INTEGER
Count ← 0
FOR Index ← 1 TO 25
   IF Ward[Index].IntensiveCare = TRUE THEN
      Count ← Count + 1
   ENDIF
NEXT Index
OUTPUT Count
```

6. Output: 16. Ptr holds the address of X, not a copy of its value. When X changes to 8, Ptr^ (the value at that address) is also 8, so Ptr^ * 2 is 16.

  1. Each letter is either guessed or not, the order of guessing does not matter for the check, and a letter cannot meaningfully be guessed twice, so an unordered collection with no duplicates fits exactly. Membership tells you whether a new guess is a repeat. The difference WordLetters − Guessed gives the letters of the word not yet guessed; when it is empty, the player has won.

```python
class Car:
    def __init__(self, registration, make, year, electric):
        self.registration = registration
        self.make = make
        self.year = year
        self.electric = electric

cars = [Car("AB21 XYZ", "Nissan", 2021, True),
        Car("CD19 PQR", "Ford", 2019, False),
        Car("EF18 LMN", "Tesla", 2018, True)]

for car in cars:
    if car.electric and car.year > 2020:
        print(car.registration)      # AB21 XYZ
```

9. One good design:

```pseudocode
TYPE WeekDay = (Monday, Tuesday, Wednesday, Thursday, Friday)
TYPE GroupSet = SET OF STRING

TYPE Lesson
   DECLARE Day : WeekDay
   DECLARE Period : INTEGER
   DECLARE Subject : STRING
   DECLARE Room : STRING
   DECLARE Groups : GroupSet
ENDTYPE

DECLARE Timetable : ARRAY[1:200] OF Lesson
```

`WeekDay` is enumerated: five fixed, ordered values, and no invalid day can be stored. `Period` is an `INTEGER` (1 to 6, can be range-checked and compared). `Room` is a `STRING` because rooms such as `"S12"` contain letters. The groups form a set because a group either attends or does not, with no order or repetition, and membership ("does 12B attend?") is the question asked. `Lesson` is a record because the five items describe one lesson and must be kept together; an array of `Lesson` holds the timetable.

10. In a linked list the items are not stored in consecutive locations in order; each node must record where the next node is. A pointer holds that memory address, so nodes can be created anywhere in memory as the list grows and inserted or deleted by changing pointers rather than moving data. In an array implementation, each node is a record with a data field and an INTEGER pointer field holding the array index of the next node; a special value (such as 0 or −1) acts as the null pointer, and a separate variable holds the index of the first node (the start pointer).

How well do you know this?

Where this leads

Console

Search notes, courses and tools, or run an action