Wyjątki ewoluowały jako uogólnienie błędów. Pierwszy język programowania do włączenia mechanizmu wyjątków był Lisp na początku 1970 roku. Jest dobre podsumowanie w A Pattern of Language Evolution autorstwa Gabriela i Steele. Wyjątki (które nie były jeszcze nazywane wyjątkami) wynikały z potrzeby określenia zachowania programu w przypadku wystąpienia błędu. Jedną z możliwości jest zatrzymanie programu, ale nie zawsze jest to pomocne. Implementacje Lisp tradycyjnie miały sposób na wejście do debuggera w przypadku błędu, ale czasami programiści chcieli włączyć obsługę błędów w swoim programie. Tak więc implementacje Lisp z lat 60. XX wieku miały sposób powiedzieć „zrób to, a jeśli wystąpi błąd, zrób to zamiast tego”. Początkowo błędy pochodziły z prymitywnych funkcji, ale programiści uznali za celowe wyzwolenie błędu, aby pominąć część programu i przejść do procedury obsługi błędów.
W 1972 roku nowoczesna forma obsługi wyjątków w Lisp pojawiła się w MacLisp: throwi catch. Software Preservation Grupa wymienia wiele materiałów na wczesnych implementacjach Lisp, w tym The maclisp Reference Manual Wersja 0 Davida Księżyca . Prymitywy catchi throwsą opisane w §5.3 str.43.
catchto funkcja LISP do wykonywania ustrukturyzowanych wyjść nielokalnych. (catch x)ocenia xi zwraca swoje wartości, z wyjątkiem tego, że jeśli podczas oceny x (throw y)powinien zostać oceniony, catchnatychmiast wraca ybez dalszej oceny x.
catchmoże być również użyty z argumentem econd, bez oceny, który służy jako znacznik do rozróżnienia zagnieżdżonych połowów. (…)
throwjest używany catchjako ustrukturyzowany nielokalny mechanizm wyjścia.
(throw x)ocenia xi zwraca wartość z powrotem do najnowszej catch.
(throw x <tag>)zwraca wartość xpowrotu do najnowszego catchoznaczonego <tag>lub nieoznaczonego.
Nacisk kładziony jest na nielokalny przepływ kontroli. Jest to forma goto (goto tylko w górę), która jest również nazywana skokiem . Metafora jest taka, że jedna część programu wyrzuca wartość, aby zwrócić ją do procedury obsługi wyjątków, a procedura obsługi wyjątków przechwytuje tę wartość i zwraca ją.
Obecnie większość języków programowania pakuje znacznik i wartość w obiekt wyjątku i łączy mechanizm przechwytywania z mechanizmem obsługi.
Wyjątki niekoniecznie są błędami. Są sposobem na wyjście z bloku kodu i otaczających go bloków, uciekając, dopóki nie zostanie osiągnięty moduł obsługi wyjątku. To, czy taka rzecz jest uważana za „błąd” w sensie intuicyjnym, jest subiektywne.
W niektórych językach rozróżnia się terminy „błąd” i „wyjątek”. Na przykład niektóre dialekty Lisp muszą zarówno throwzgłosić wyjątek (przepływ sterowania dla użytkowników, mający na celu wykonanie nielokalnego wyjścia w sposób, który nie oznacza, że coś poszło „nie tak”), a signaltakże zgłoszenie błędu (co oznacza, że coś poszło „nie tak” i może spowodować zdarzenie debugowania).