Programowanie symboliczne to paradygmat programowania, w którym program komputerowy może dokonywać zmian we własnych podstawowych elementach, np. wyrażeniach kodu źródłowego, w taki sam sposób, w jaki dokonuje się operacji na dowolnych danych[1]. Jest to sposób programowania, w którym rozwiązując problemy obliczeniowe, nie tylko tworzymy nowe programy, lecz również dostosowujemy język do efektywnego wyrażania rozwiązań tych problemów. Takie podejście leży u podstaw techniki zwanej metaprogramowaniem.
W językach programowania obsługujących paradygmat symboliczny, takich jak Lisp czy Prolog, można decydować, które fragmenty kodu źródłowego zostaną poddane uruchamianiu w procesie obliczania wartości wyrażeń, a które zostaną potraktowane jak dane. W rezultacie możliwe jest modyfikowanie oryginalnego kodu źródłowego programu (a dokładniej jego pamięciowej reprezentacji) z użyciem odpowiednich konstrukcji (w Lispie będą to makra i formy specjalne) zanim wyrażenia będą przeliczane. Pozwala to na budowanie złożonych systemów obliczeniowych na podstawie przejrzystych, wyspecjalizowanych części realizujących poszczególne zadania. Mogą to być niewielkie udogodnienia rozszerzające możliwości języka (np. nowe rodzaje pętli bądź innych konstrukcji sterujących), ale także zorientowane pod kątem wyrażania konkretnych rozwiązań problemów języki dziedzinowe.
Symboliczny paradygmat programowania zawdzięcza powstanie językowi Lisp, w którym wyrażenia kodu źródłowego mogą być interpretowane jako formy przeznaczone do przeliczenia w toku uruchamiania programu bądź jako formy zwykłe, czyli dane, na których program operuje. Programista jest w stanie wybierać, w jakich warunkach dany fragment kodu będzie potraktowany jak dane, a w jakich realizowany. Wynika to z operacji leżących u podstaw interpretacyjnych tego języka: eval (wartościowanie przekazanych danych, traktując je jak kod źródłowy) i quote (wyłączenie wartościowania fragmentu kodu źródłowego i potraktowanie go jak dane).
Przykładem elementów symbolicznych w innych językach programowania mogą być znane z języka C makra preprocesora lub konstrukcje typu eval w skryptowych językach powłokowych bądź nowoczesnych językach wieloparadygmatowych, takich jak Python czy Ruby.