User-defined data types
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.
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
Seasoncan only hold the four seasons, so an invalid value such as"Smmer"cannot be stored. - It makes code readable and maintainable.
ThisSeason ← Winterexplains itself;ThisSeason ← 3does not. - It can be reused. Once
StudentRecordis 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.
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.
| Type | Composite? | What it holds | Pseudocode keyword |
|---|---|---|---|
| Enumerated | Non-composite | One value from a fixed, ordered list | TYPE Name = (v1, v2, ...) |
| Pointer | Non-composite | A memory address of a value of a stated type | TYPE Name = ^DataType |
| Record | Composite | Several named fields, possibly of different types | TYPE ... ENDTYPE |
| Set | Composite | An unordered collection of distinct values of one type | TYPE Name = SET OF DataType |
| Class / object | Composite | Attributes plus the methods that act on them | CLASS ... ENDCLASS |
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:
| Expression | Meaning |
|---|---|
TYPE TIntPointer = ^INTEGER | declares 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
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:
| Operation | Meaning | Example with , |
|---|---|---|
| Membership | is a value in the set? | 2 is in ; 4 is not |
| Union | everything in either set | |
| Intersection | everything in both sets | |
| Difference | in the first but not the second |
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
- 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).
- Ask whether a value comes from a small fixed list of named options. If so, use an enumerated type.
- Ask whether the program only needs to know membership of a collection, with no order and no repeats. If so, use a set.
- 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).
- Write the declaration in pseudocode, giving every field an identifier and an appropriate built-in type, and justify each choice in one phrase.
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
ENDIFMarks 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.
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
ENDTYPEThe 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 IndexThe 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 DictionaryStudy 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, " ", BVariable A is stored at address 3000 and B at address 3004. Complete a trace and state the output.
Solution
| Statement | A | B | P | Q | Notes |
|---|---|---|---|---|---|
A ← 10 | 10 | ||||
B ← 20 | 10 | 20 | |||
P ← ^A | 10 | 20 | 3000 | P holds the address of A | |
Q ← ^B | 10 | 20 | 3000 | 3004 | |
P^ ← P^ + Q^ | 30 | 20 | 3000 | 3004 | value at 3000 becomes 10 + 20 |
Q ← P | 30 | 20 | 3000 | 3000 | Q now also points at A |
Q^ ← Q^ * 2 | 60 | 20 | 3000 | 3000 | value 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.
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, soSize > Smallcan be used when pricing. Non-composite. - Toppings: set,
TYPE ToppingSet = SET OF Topping(whereToppingis 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 : STRINGandTimeOrdered : DATE(or aSTRINGtime). 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.
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
ENDPROCEDUREThe 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- "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...ENDTYPEwithDECLAREinside for a record; brackets with no quotes for an enumerated type;^before the type name for a pointer;SET OFthenDEFINEfor a set. - Choose field types sensibly and be ready to justify them: telephone numbers, ISBNs and codes with leading zeros are
STRING; money isREAL; yes/no isBOOLEAN. - In pointer traces, keep the address and the value at the address in separate columns.
- 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;^Xis the address ofX;P^is the value P points to. - Record:
TYPE ... ENDTYPEwithDECLAREfields; access with dot notation; the basis of files of records. - Set:
TYPE LetterSet = SET OF CHARthenDEFINE; 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
-
State the difference between a composite and a non-composite data type, and give two examples of each.
-
Write pseudocode to declare an enumerated type
Directionholding North, East, South and West, and a variableFacingof that type. Write a statement that turnsFacingclockwise by one step, assumingFacingis notWest. -
Explain why an enumerated type is more appropriate than a
STRINGfor storing the day of the week. -
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 typePatient, and declare an arrayWardable to hold 25 patients. -
Using your declaration from question 4, write pseudocode to count and output how many patients in
Wardare in intensive care. -
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 -
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.
-
Write a Python class to act as a record for a
Carwith fieldsregistration(string),make(string),year(integer) andelectric(Boolean). Write code to create a list of three cars and output the registration of every electric car built after 2020. -
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.
-
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
-
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).
```pseudocodeTYPE 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.
```pseudocodeTYPE 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.
-
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 − Guessedgives the letters of the word not yet guessed; when it is empty, the player has won.
```pythonclass 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).