Appendix D: Common Compiler Errors and Fixes
This appendix catalogs the most common Free Pascal compiler errors and warnings you will encounter, organized by category. For each error, you will find the exact error message, a plain-English explanation, the most common causes, and a concrete example showing both the broken code and the fix.
D.1 Syntax Errors
Error: "Syntax error, ; expected but ELSE found"
What it means: You placed a semicolon before an else clause. In Pascal, if...then...else is a single statement, and a semicolon terminates the then branch prematurely.
Common cause: Writing a semicolon after the statement before else.
{ WRONG }
if X > 0 then
WriteLn('Positive'); { <-- semicolon here ends the if statement }
else
WriteLn('Non-positive');
{ FIX }
if X > 0 then
WriteLn('Positive') { <-- no semicolon before else }
else
WriteLn('Non-positive');
Error: "Syntax error, END expected but identifier found"
What it means: The compiler expected an end keyword to close a begin block, record, or class definition, but found something else instead.
Common cause: Mismatched begin/end pairs. You opened a begin block but forgot to close it, or you have an extra begin.
{ WRONG }
procedure DoWork;
begin
if Ready then
begin
Process;
{ Missing 'end' for this begin }
WriteLn('Done');
end;
{ FIX }
procedure DoWork;
begin
if Ready then
begin
Process;
end;
WriteLn('Done');
end;
Tip: Use consistent indentation to make begin/end mismatches visible. Lazarus highlights matching pairs.
Error: "Syntax error, ) expected but , found"
What it means: A function call or declaration has a parenthesis mismatch.
Common cause: Missing closing parenthesis in a nested function call, or using commas where semicolons are expected in a declaration.
{ WRONG }
WriteLn(IntToStr(X + Y); { Missing closing paren for IntToStr }
{ FIX }
WriteLn(IntToStr(X + Y));
Error: "Syntax error, BEGIN expected but procedure found"
What it means: The compiler reached a procedure/function declaration where it expected the main program's begin block.
Common cause: Missing begin for the main program block, or declaring a procedure after the main begin.
{ WRONG }
program Test;
procedure Helper; { OK }
begin
WriteLn('helper');
end;
WriteLn('main'); { Error: no begin before this }
end.
{ FIX }
program Test;
procedure Helper;
begin
WriteLn('helper');
end;
begin { Main program begin }
WriteLn('main');
end.
Error: "Syntax error, OF expected but ; found"
What it means: A case statement is missing the of keyword.
{ WRONG }
case Choice;
1: WriteLn('One');
end;
{ FIX }
case Choice of
1: WriteLn('One');
end;
Error: "Unexpected end of file"
What it means: The compiler reached the end of the source file while still expecting more tokens.
Common causes:
- Missing the final end. (with a period) at the end of a program or unit.
- An unclosed string literal (missing closing quote).
- An unclosed comment ({ without }, or (* without *)).
{ WRONG }
program Test;
begin
WriteLn('Hello');
end { Missing period }
{ FIX }
program Test;
begin
WriteLn('Hello');
end. { Period terminates the program }
Error: "Syntax error, STRING expected but identifier found"
What it means: Often caused by writing string in a context where the compiler expects a type name, or by a mode mismatch.
Common cause in ObjFPC mode: Using generic syntax without the generic keyword.
{ WRONG in {$mode objfpc} }
type
TList<T> = class { Delphi syntax }
end;
{ FIX }
type
generic TList<T> = class { ObjFPC syntax }
end;
D.2 Type Errors
Error: "Incompatible types: got X expected Y"
What it means: You are assigning or passing a value of one type where a different type is required.
{ WRONG }
var
S: String;
N: Integer;
begin
S := 'Hello';
N := S; { Cannot assign String to Integer }
end;
{ FIX }
N := Length(S); { Or whatever conversion is appropriate }
N := StrToInt(S); { If S contains a number }
Error: "Incompatible types: got ShortString expected AnsiString"
What it means: You are mixing string types. This commonly occurs when {$H+}` and `{$H-} modes are mixed.
{ FIX: Ensure consistent {$H+} at the top of all units }
{$mode objfpc}{$H+}
Error: "Error: Operator is not overloaded: X + Y"
What it means: You used an operator with types for which that operator is not defined.
{ WRONG }
var
A: Boolean;
B: Integer;
begin
B := A + 5; { Cannot add Boolean and Integer }
end;
{ FIX }
if A then
B := 1 + 5
else
B := 0 + 5;
{ Or use Ord: }
B := Ord(A) + 5;
Error: "Can't assign values to const variable"
What it means: You tried to modify a const parameter or a true constant.
{ WRONG }
procedure Process(const X: Integer);
begin
X := X + 1; { Cannot modify const parameter }
end;
{ FIX: Use a local variable }
procedure Process(const X: Integer);
var
LocalX: Integer;
begin
LocalX := X + 1;
end;
{ Or change to a value parameter: }
procedure Process(X: Integer);
begin
X := X + 1; { This modifies the local copy }
end;
Error: "Incompatible type for arg no. N: got X expected var Y"
What it means: You passed an expression or a constant to a var parameter. var parameters require a variable (an lvalue) because the procedure may modify it.
{ WRONG }
procedure Increment(var X: Integer);
begin
Inc(X);
end;
Increment(5); { Cannot pass a literal to var }
Increment(A + B); { Cannot pass an expression to var }
{ FIX }
var
N: Integer;
begin
N := 5;
Increment(N); { Pass a variable }
end;
Error: "Error: Type mismatch between X and Y"
What it means: Two types that look the same are actually different named types.
type
TAge = Integer;
TCount = Integer;
{ These are distinct types in {$mode objfpc} with strict type checking.
In most cases this works fine, but if you see this error, cast explicitly: }
var
A: TAge;
C: TCount;
begin
A := 25;
C := TCount(A); { Explicit cast }
end;
Error: "Illegal type conversion: X to Y"
What it means: You used an invalid typecast. Not all types can be cast to each other.
{ WRONG }
var
S: String;
N: Integer;
begin
N := Integer(S); { Cannot cast String to Integer }
end;
{ FIX: Use a conversion function }
N := StrToInt(S);
D.3 Scope and Declaration Errors
Error: "Identifier not found X"
What it means: You used a name that has not been declared in the current scope.
Common causes:
- Typo in the identifier name.
- Forgot to declare a variable.
- Forgot to add a unit to the uses clause.
- The identifier is declared in a different scope (inside another procedure, in the implementation section of a unit, etc.).
{ WRONG }
begin
Writeln(Greeting); { 'Greeting' not declared }
end;
{ FIX }
var
Greeting: String;
begin
Greeting := 'Hello';
WriteLn(Greeting);
end;
{ Or if it's in a unit: }
uses
MyUtils; { Add the unit that declares Greeting }
Error: "Duplicate identifier X"
What it means: Two declarations in the same scope share the same name.
{ WRONG }
var
Count: Integer;
Count: Integer; { Duplicate }
{ FIX: Remove the duplicate, or use different names }
var
Count: Integer;
ItemCount: Integer;
Error: "Forward declaration not solved X"
What it means: You declared a procedure or function with forward but never provided the implementation.
{ WRONG }
procedure DoWork; forward;
{ ... but DoWork is never implemented }
{ FIX: Provide the implementation }
procedure DoWork; forward;
procedure DoWork;
begin
WriteLn('Working');
end;
This error also appears for methods declared in a class definition but not implemented.
Error: "Unit X not found"
What it means: The compiler cannot find the unit you listed in the uses clause.
Common causes: - Typo in the unit name. - The unit is not installed (e.g., a Lazarus-specific unit used in a plain FPC project). - The unit search path does not include the directory containing the unit.
{ WRONG }
uses
SysUtil; { Typo: should be SysUtils }
{ FIX }
uses
SysUtils;
To add a custom unit search path, use the -Fu compiler option or set it in Lazarus under Project > Project Options > Compiler Options > Paths > Other unit files.
Error: "Illegal expression" or "Variable identifier expected"
What it means: A statement or expression appears where only a variable is allowed.
{ WRONG }
5 := X; { Cannot assign to a literal }
A + B := C; { Cannot assign to an expression }
{ FIX }
X := 5;
C := A + B;
D.4 File and I/O Errors
Error: "File not found X"
What it means (compile time): An {$I filename} include directive or uses clause references a file that does not exist at the expected path.
Common cause: Wrong filename, wrong directory, or missing the .inc extension.
{ WRONG }
{$I missing_file.inc}
{ FIX: Verify the file exists and the path is correct }
{$I settings.inc}
Runtime Error 2: "File not found"
What it means: Your program tried to open a file that does not exist (via Reset on a nonexistent file).
{ WRONG }
AssignFile(F, 'nonexistent.txt');
Reset(F); { Runtime error if file does not exist }
{ FIX: Check first }
AssignFile(F, 'data.txt');
if FileExists('data.txt') then
begin
Reset(F);
{ ... }
CloseFile(F);
end
else
WriteLn('File not found!');
Runtime Error 5: "File access denied"
What it means: The program does not have permission to open the file, or the file is locked by another process.
Fix: Check file permissions, ensure no other program has the file open, and verify you are not trying to write to a read-only location.
Runtime Error 100: "Disk read error" / Runtime Error 101: "Disk write error"
What it means: A physical I/O error occurred, or you attempted to read past the end of a file, or write to a full disk.
Fix: Use {$I-} and check IOResult for graceful error handling:
{$I-}
Reset(F);
if IOResult <> 0 then
begin
WriteLn('Cannot open file');
Halt(1);
end;
{$I+}
D.5 OOP Errors
Error: "Abstract method X not implemented"
What it means: You created an instance of a class that has abstract methods without implementing them in a descendant.
{ WRONG }
type
TShape = class
function Area: Double; virtual; abstract;
end;
var
S: TShape;
begin
S := TShape.Create; { Error: TShape has abstract methods }
end;
{ FIX: Create a descendant that implements the abstract method }
type
TCircle = class(TShape)
Radius: Double;
function Area: Double; override;
end;
function TCircle.Area: Double;
begin
Result := Pi * Radius * Radius;
end;
var
S: TShape;
begin
S := TCircle.Create; { OK: TCircle implements Area }
end;
Error: "No matching implementation for interface method X"
What it means: A class declares that it implements an interface but is missing one or more of the interface's methods.
{ WRONG }
type
IDrawable = interface
procedure Draw;
procedure Resize(Factor: Double);
end;
TWidget = class(TInterfacedObject, IDrawable)
procedure Draw;
{ Missing: procedure Resize(Factor: Double); }
end;
{ FIX: Implement all interface methods }
TWidget = class(TInterfacedObject, IDrawable)
procedure Draw;
procedure Resize(Factor: Double);
end;
Error: "method X not found in class Y"
What it means: You tried to override or call a method that does not exist in the parent class.
{ WRONG }
type
TBase = class
procedure DoWork;
end;
TChild = class(TBase)
procedure DoStuff; override; { 'DoStuff' does not exist in TBase }
end;
{ FIX: Match the parent method name exactly }
TChild = class(TBase)
procedure DoWork; override; { Correct name }
end;
Note: The parent method must also be declared virtual or dynamic for override to work.
Error: "Calling convention mismatch"
What it means: The method signature in the implementation does not match the declaration, including calling convention modifiers.
Fix: Ensure the implementation exactly matches the declaration, including all modifiers (virtual, override, reintroduce, stdcall, etc.).
Error: "Constructor or Destructor must be a method"
What it means: You declared a constructor or destructor outside of a class definition.
{ WRONG }
constructor Create; { Not inside a class }
begin
end;
{ FIX: Declare inside a class }
type
TMyClass = class
constructor Create;
end;
constructor TMyClass.Create;
begin
inherited Create;
end;
Error: "Virtual or dynamic methods not allowed in records"
What it means: Records (even advanced records with methods) do not support virtual method dispatch. Only classes do.
{ WRONG }
type
TPoint = record
X, Y: Double;
procedure Draw; virtual; { Not allowed in record }
end;
{ FIX: Use a class, or remove 'virtual' }
type
TPoint = record
X, Y: Double;
procedure Draw; { Regular method is fine }
end;
D.6 Warnings and Hints
While not errors, these messages indicate potential problems. Good practice is to compile with zero warnings.
Warning: "Variable X does not seem to be initialized"
What it means: You are using a local variable before assigning it a value. The variable contains garbage.
{ WARNING }
function Compute: Integer;
var
Total: Integer; { Not initialized }
begin
Total := Total + 5; { Warning: Total is garbage here }
Result := Total;
end;
{ FIX }
function Compute: Integer;
var
Total: Integer;
begin
Total := 0; { Initialize first }
Total := Total + 5;
Result := Total;
end;
Warning: "Function result variable does not seem to be set"
What it means: A function might return without setting Result (or the function name).
{ WARNING }
function IsPositive(X: Integer): Boolean;
begin
if X > 0 then
Result := True;
{ What if X <= 0? Result is unset }
end;
{ FIX }
function IsPositive(X: Integer): Boolean;
begin
Result := X > 0;
end;
Hint: "Local variable X not used"
What it means: You declared a variable but never referenced it.
Fix: Remove the unused variable, or prefix it with an underscore if it is intentionally unused (some coding standards).
Hint: "Value parameter X is assigned but never used"
What it means: You assigned a value to a value parameter, but the result is never used. Since value parameters are copies, the assignment has no effect outside the procedure.
Fix: If you intend to modify the caller's variable, change to a var parameter. Otherwise, remove the assignment.
Warning: "Comparison always evaluates to True/False"
What it means: The compiler detected a comparison whose result is known at compile time.
{ WARNING }
var
B: Byte; { Range 0..255 }
begin
if B < 0 then { Always False: Byte cannot be negative }
WriteLn('Negative');
end;
{ FIX: Remove the dead code or use an appropriate type }
var
N: Integer; { Use Integer if negative values are possible }
begin
if N < 0 then
WriteLn('Negative');
end;
Warning: "Implicit string type conversion"
What it means: The compiler is converting between string types (e.g., ShortString to AnsiString) automatically. This may lose data or have performance implications.
Fix: Use explicit conversions, or ensure consistent string types and {$H+} settings across all units.
D.7 Runtime Errors
These are not compiler errors but errors that occur when the program runs.
| Code | Name | Common Cause |
|---|---|---|
| 1 | Invalid function number | Obsolete DOS function call |
| 2 | File not found | Reset on nonexistent file |
| 3 | Path not found | Directory does not exist |
| 4 | Too many open files | OS file handle limit reached |
| 5 | Access denied | Permission problem |
| 6 | Invalid file handle | Using a file variable that was not opened |
| 100 | Disk read error | I/O failure during read |
| 101 | Disk write error | I/O failure during write or disk full |
| 103 | File not open | Read/Write on a file that was not opened |
| 104 | File not open for input | Read on a file opened with Rewrite |
| 105 | File not open for output | Write on a file opened with Reset |
| 106 | Invalid numeric format | Read of a number from text with invalid characters |
| 200 | Division by zero | Integer div or mod by zero |
| 201 | Range check error | Array index out of bounds or value out of range (with {$R+}) |
| 202 | Stack overflow | Too-deep recursion or very large local variables |
| 203 | Heap overflow | New/GetMem failed — out of memory |
| 204 | Invalid pointer operation | Dispose/FreeMem on invalid pointer |
| 205 | Floating point overflow | Result too large for floating-point type |
| 207 | Invalid floating point operation | e.g., Sqrt(-1), Ln(0) |
| 210 | Object not initialized | Calling a method on a nil object reference |
| 211 | Call to abstract method | Called an abstract method at runtime |
| 215 | Arithmetic overflow | Integer overflow (with {$Q+}) |
| 216 | Access violation | Dereferencing nil or invalid pointer |
| 217 | Unhandled exception | An exception propagated to the top level |
| 227 | Assertion failed | Assert(False) or condition evaluated to False |
Enabling Runtime Checks
During development, enable all runtime checks to catch bugs early:
{$R+} { Range checking }
{$Q+} { Overflow checking }
{$S+} { Stack checking }
Disable them in release builds for performance:
{$R-}
{$Q-}
{$S-}
Or in Lazarus: Project > Project Options > Compiler Options > Debugging > Check for range/overflow.
D.8 Troubleshooting Strategy
When you encounter a compiler error you do not recognize:
- Read the full error message carefully. It includes the file name, line number, and column.
- Look at the line indicated and the line before it. Many errors are caused by a mistake on the previous line (e.g., a missing semicolon).
- Fix the first error only, then recompile. Later errors are often cascading consequences of the first one.
- Check
begin/endbalance. Mismatched blocks cause confusing errors far from the actual problem. - Check the
usesclause. Many "identifier not found" errors mean you forgot to include the right unit. - Search the Free Pascal documentation. The error code (e.g., "Error 3036") is documented in the FPC user manual.
- Ask for the verbose error. Compile with
fpc -vewto see errors, warnings, and notes all at once.
This appendix covers the errors you will encounter most frequently. As you gain experience, you will recognize these patterns instantly and fix them in seconds.