November 11, 2026•8 min read

Pascal Dynamic Arrays: the Sixth Score That Never Got Counted

Six scores go in, five get counted, and the mean prints 78.0 because a fixed array stopped reading at MAX. SetLength gives the array the length the data asks for, and it also changes what assignment means.

Read in Indonesian

You feed a program six scores and it reports five. The file scores.txt holds 72, 85, 91, 64, 78 and 67, one per line. The program prints a count of 5 and a mean of 78.0, with no crash and no warning. The true mean is 76.2, which is 457 divided by 6.

PASCAL
program average;

{ Version 1: a fixed array of five, and a sixth score that never lands. }

const
  MAX = 5;

var
  scores: array[1..MAX] of integer;
  n, i, sum: integer;

begin
  n := 0;
  sum := 0;
  for i := 1 to MAX do
    if not eof then
    begin
      readln(scores[i]);
      n := n + 1;
      sum := sum + scores[i];
    end;
  writeln('count: ', n);
  writeln('mean: ', sum / n:0:1);
end.
Output
$ fpc average.pas && ./average < scores.txt
count: 5
mean: 78.0

The constant MAX stopped the for loop after five reads, and the sixth line stayed unread in the file. A static array has its size fixed when you write the program, and the loop obeys that size without complaint. Nothing in the file or the compiler flags the missing line, so the only way to catch it is to compare the output with the answer you expect.

Key takeaways

  • A fixed array[1..MAX] stops reading at MAX without a warning, which is how six scores produced the mean of five.
  • A dynamic array starts with Length 0 and equal to nil; SetLength gives it a length, and the new elements arrive as zero, not as garbage.
  • Its indexes run from 0 to High, where a static array[1..5] runs from 1 to 5.
  • b := a copies the pointer, so writing through b changes a; Copy(a, start, count) is what makes independent storage.
  • SetLength(a, 0) releases what a points at, but a second variable holding the same block keeps the data alive until the last reference goes.
  • Nothing stops a write past the end by default: build with -Cr to turn it into Runtime error 201 instead of silent corruption.

Give the array a length at run time

A dynamic array is declared as array of integer, with no bounds. The variable starts empty: its Length is 0 and it equals nil, the pointer value that refers to nothing. SetLength gives it a size.

PASCAL
program explore;

var
  a, b: array of integer;
  i: integer;

begin
  writeln('before SetLength: Length = ', Length(a), ', a = nil is ', a = nil);
  SetLength(a, 3);
  writeln('after SetLength(a, 3): Length = ', Length(a), ', Low = ', Low(a), ', High = ', High(a));
  write('elements:');
  for i := Low(a) to High(a) do
    write(' ', a[i]);
  writeln;
  writeln('sizeof(a) = ', sizeof(a));
  a[0] := 10;
  a[1] := 20;
  a[2] := 30;
  SetLength(a, 5);
  writeln('grown to 5: Length = ', Length(a), ', kept ', a[0], ' ', a[1], ' ', a[2], ', new ', a[3], ' ', a[4]);
  b := a;
  b[0] := 99;
  writeln('b[0] = 99, a[0] = ', a[0], ', same storage = ', @b[0] = @a[0]);
  SetLength(a, 0);
  writeln('after SetLength(a, 0): Length = ', Length(a), ', a = nil is ', a = nil);
  writeln('b is still ', Length(b), ' elements, b[0] = ', b[0]);
end.
Output
$ fpc explore.pas && ./explore
before SetLength: Length = 0, a = nil is TRUE
after SetLength(a, 3): Length = 3, Low = 0, High = 2
elements: 0 0 0
sizeof(a) = 8
grown to 5: Length = 5, kept 10 20 30, new 0 0
b[0] = 99, a[0] = 99, same storage = TRUE
after SetLength(a, 0): Length = 0, a = nil is TRUE
b is still 5 elements, b[0] = 99

The indexes run from 0 to 2, where array[1..5] runs from 1 to 5. New elements are zero, not leftover garbage. Growing from 3 to 5 keeps 10 20 30 and fills the two new slots with zeros.

sizeof(a) is 8 whatever the length, because the variable holds a pointer to the data and not the data. A static array[1..5] of integer occupies 10 bytes here: five integers of two bytes each, Free Pascal's default size. The dynamic version replaces those 10 bytes with an 8-byte pointer plus a heap block, which is memory the program requests while it runs.

Two names, one array

The assignment b := a copies the pointer, not the elements. After b[0] := 99, a[0] reads 99 too, and @b[0] = @a[0] is TRUE: both names reach the same storage. SetLength(a, 0) empties a, yet b still holds its five elements. Pascal counts references to the block and frees it when the last one goes.

Copy gives you independent storage. Copy(a, 1, 3) on an array holding 3 4 5 6 7 8 returns 4 5 6, because the start index is 0-based like the array itself. Copy(a, 4, 3) returns only 7 8, because only two elements were left. Changing the copy's first element to 999 left the original at 3, and @original[0] = @copy[0] was FALSE.

The exception: nothing guards the end

PASCAL
program probe4;

{ Out-of-bounds with an index the compiler cannot see. }

var
  dyn: array of integer;
  i: integer;

begin
  SetLength(dyn, 3);
  i := Length(dyn);
  writeln('writing at index ', i, ' of length ', Length(dyn));
  dyn[i] := 42;
  writeln('survived, dyn[i] = ', dyn[i]);
end.
Output
$ fpc probe4.pas && ./probe4
writing at index 3 of length 3
survived, dyn[i] = 42

Index 3 on a length-3 array writes into memory the array does not own, and the program exits with status 0. Range checking is off by default. Build with -Cr and the same write stops the program:

Output
$ fpc -Cr probe4.pas && ./probe4
writing at index 3 of length 3
Runtime error 201 at $00000000004011D9
  $00000000004011D9
  $000000000042308C

The exit status is 201. A constant index is caught earlier: with -Cr, writing index 4 into a fixed array[1..3] stops compilation with Error: range check error while evaluating constants (4 must be between 1 and 3).

Make the check fail first

Save this as check_average.pas. It reads a program's output on stdin and compares it to two expected lines. For each line it prints PASS or FAIL, and a line that never arrives counts as a failure with the note "no such line".

PASCAL
program check_average;

{ Reads the program's output on stdin and compares it to the expected lines. }

var
  expected: array[1..2] of string;
  line: string;
  i, passed, failed, total: integer;

procedure Report(ok: boolean; n: integer; want, got: string);
begin
  if ok then
  begin
    writeln('PASS  line ', n, ': ', want);
    passed := passed + 1;
  end
  else
  begin
    if got = '' then
      writeln('FAIL  line ', n, ': expected "', want, '", no such line')
    else
      writeln('FAIL  line ', n, ': expected "', want, '", got "', got, '"');
    failed := failed + 1;
  end;
end;

begin
  expected[1] := 'count: 6';
  expected[2] := 'mean: 76.2';

  passed := 0;
  failed := 0;
  total := 2;
  i := 0;

  while (not eof) and (i < total) do
  begin
    readln(line);
    i := i + 1;
    Report(line = expected[i], i, expected[i], line);
  end;

  while i < total do
  begin
    i := i + 1;
    Report(false, i, expected[i], '');
  end;

  writeln;
  writeln(passed, ' of ', total, ' checks passed');
  if failed > 0 then
    ExitCode := 1;
end.

Compile the checker and version 1 of average.pas with fpc, then pipe one into the other.

Output
$ ./average < scores.txt | ./check_average
FAIL  line 1: expected "count: 6", got "count: 5"
FAIL  line 2: expected "mean: 76.2", got "mean: 78.0"

0 of 2 checks passed

The checker exits with status 1 here, so a script can gate on it.

Switch to a dynamic array

Replace average.pas with version 2. The fixed array becomes array of integer, and SetLength(scores, n + 1) runs before each read.

PASCAL
program average;

{ Version 2: the array grows to fit whatever arrives. }

var
  scores: array of integer;
  n, sum: integer;

begin
  n := 0;
  sum := 0;
  while not eof do
  begin
    SetLength(scores, n + 1);
    readln(scores[n]);
    sum := sum + scores[n];
    n := n + 1;
  end;
  writeln('count: ', Length(scores));
  writeln('mean: ', sum / n:0:1);
end.

Compile it and run the same pipe.

Output
$ ./average < scores.txt | ./check_average
PASS  line 1: count: 6
PASS  line 2: mean: 76.2

2 of 2 checks passed

The checker now exits with status 0. Version 2 reports Length(scores) as its count, so the number comes from the array itself. The loop has no MAX to stop it, and the array ends up as long as the file.

Choosing between them

Ask who decides the count. If you decide it when you write the program, use array[1..N] with a constant. If the data decides it, use array of and index from 0. Build with -Cr while you test, because a dynamic array does not stop a write past its end by default.

Get better at shipping code.

Join the other developers getting tech updates, tips, and relaxed tutorials in their inbox every week.

Stay in the loop

Tech updates, coding tips, and relaxed insights straight to your inbox every week.

No spam. Unsubscribe anytime.

Read next

See all
Struktur DataWhat a Linked List Actually Is

A linked list is a linear chain of nodes, each holding data and a pointer to the next node. Here is what that chain looks like, what it costs next to an array, and the same minimal implementation in Java, Python, and JavaScript.

For developers who learn by struggling

© 2026 Inva.dev.