Recursion (özyineleme), bir fonksiyonun problemi çözmek için kendi kendisini, her seferinde biraz daha küçük bir versiyonuyla yeniden çağırmasıdır. Yani fonksiyon "bu işi tek seferde bitiremiyorum ama daha küçük bir parçasını çözersem, geri kalanı için aynı yöntemi tekrar kullanabilirim" mantığıyla çalışır. Bu yazıda recursion'ı önce günlük hayattan bir benzetmeyle, sonra kod üzerinden adım adım göreceğiz; hiçbir noktada "zor bir konu" gibi ele almadan, gözünüzde canlandırabileceğiniz somut adımlarla ilerleyeceğiz.
Amaç, recursion'ı ezberlemeden, fonksiyonun her çağrıldığında ne yaptığını gerçekten görerek anlamanız. Faktöriyel örneğiyle kodu satır satır izleyecek, call stack'in nasıl işlediğini numaralı adımlarla takip edecek ve recursion ile iterasyonu ne zaman seçmeniz gerektiğini bir tablo üzerinden karşılaştıracaksınız.
Recursion (Özyineleme) Nedir? Basit Bir Benzetmeyle Anlayalım
Recursion'ı ilk kez öğrenirken en rahat yol, onu bir kod kavramı olarak değil, önce bir görsel fikir olarak düşünmektir. İç içe geçmiş Rus bebeklerini (matruşka) ya da birbirinin içine yerleştirilmiş kutuları hayal edin. En büyük kutuyu açtığınızda içinden biraz daha küçük bir kutu çıkar. O kutuyu açtığınızda içinden yine daha küçük bir kutu çıkar. Bu, en sonunda açtığınızda içinde başka bir kutu olmayan, gerçekten en küçük ve son kutuya kadar sürer.
Bu benzetmede dikkat edilmesi gereken iki şey var: birincisi, her kutunun içeriği aslında aynı "kutu açma" işleminin daha küçük bir kopyasıdır. İkincisi, bu zincir sonsuza kadar gitmez; bir noktada gerçekten içi boş, açılacak başka bir şeyi olmayan bir kutuya ulaşırsınız. İşte recursion mantığının tam da bu iki fikre dayandığını söyleyebiliriz: aynı işlemin daha küçük bir versiyonuyla tekrar tekrar uygulanması ve bu tekrarın bir noktada kesin olarak durması.
Bu sezgisel resmi teknik tanıma taşıdığımızda şu ifadeye ulaşırız: recursion, bir fonksiyonun kendi kendisini, orijinal problemden daha küçük bir alt problemle çağırmasıdır. Fonksiyon, kendisini çağırırken elindeki veriyi biraz "küçültür" ve bu küçültme işlemi, kutu benzetmesindeki gibi bir noktada doğal olarak sona erer.
Burada önemli bir yanlış anlamayı da baştan netleştirmek gerekir: recursion, döngülerin (for, while) yerini alan rakip bir yöntem değildir. Aynı problemi genellikle bir döngüyle de çözebilirsiniz; recursion size problemi farklı bir açıdan görme imkânı sunar. Döngüde "bu işi kaç kere tekrarlayacağım" diye düşünürken, recursion'da "büyük problemi küçük bir problem ve o problemin çözümüne dayanan bir adım olarak nasıl tanımlarım" diye düşünürsünüz. İkisi de doğru sonuca ulaşabilir; seçim, problemin doğasına ve okunabilirliğe bağlıdır.
Base Case ve Recursive Case: Bir Fonksiyonun İki Yüzü
Her recursive fonksiyon, aslında iki farklı davranışı bir arada barındırır. Birincisi base case (temel durum): fonksiyonun kendini artık çağırmadığı, doğrudan bir sonuç döndürdüğü durumdur. İkincisi recursive case (özyinelemeli durum): fonksiyonun problemi küçültüp kendisini yeniden çağırdığı durumdur. Kutu benzetmesine dönersek, base case içinde başka kutu olmayan en son kutudur; recursive case ise "bu kutuyu aç, içindeki daha küçük kutuyla aynı işlemi tekrar yap" adımıdır.
Base case'in neden bu kadar kritik olduğunu basit bir şekilde şöyle özetleyebiliriz: base case, fonksiyona "artık küçülmeye devam etme, burada dur ve cevabı ver" diyen bir dur işaretidir. Bu dur işareti olmadan fonksiyon, her çağrıda kendini yeniden çağırmaya devam eder ve hiçbir zaman gerçek bir sonuca ulaşamaz. Base case eksik bırakılırsa veya problemi gerçekten küçültmeyen (yani bir süre sonra base case'e ulaşmayan) bir koşulla yanlış kurulursa, fonksiyon teorik olarak sonsuza kadar kendini çağırmaya çalışır; pratikte ise bu durum programın çökmesiyle sonuçlanır.
Bunu küçük bir sayma problemiyle somutlaştıralım: 1'den n'e kadar sayan bir fonksiyon düşünün. Recursive case şudur: "n sayısını yazdır, sonra n-1 için aynı işlemi tekrar yap." Base case ise şudur: "n sıfıra ya da başlangıç noktasına ulaştıysa artık yeni bir çağrı yapma, dur." Burada iki durum birbirini tamamlar: recursive case her adımda problemi biraz küçültür (n giderek azalır), base case ise bu küçülmenin bir sınırı olduğunu ve o sınıra ulaşıldığında işin bittiğini garanti eder. Bir recursive fonksiyon yazarken kendinize sorulacak en doğru soru şudur: "Bu fonksiyon hangi noktada kendini çağırmayı bırakacak, ve her çağrı gerçekten o noktaya bir adım daha yaklaşıyor mu?" Bu sorunun net bir cevabı yoksa, fonksiyon henüz güvenli değildir.
Python ile Faktöriyel Hesaplayan Recursive Fonksiyon

Base case ve recursive case fikrini artık gerçek bir kod üzerinde görelim. Faktöriyel, recursion öğretiminde en sık kullanılan örneklerden biridir çünkü problem doğası gereği kendi küçük versiyonuna dayanır: n sayısının faktöriyeli, n ile (n-1)'in faktöriyelinin çarpımıdır. Aşağıdaki Python fonksiyonu bu tanımı birebir koda döker:
def factorial(n):
# Base case: 0! ve 1! değeri 1'dir, burada özyineleme durur
if n <= 1:
return 1
# Recursive case: n! = n * (n-1)!
# Fonksiyon kendini n-1 ile çağırıyor, problem her adımda küçülüyor
return n * factorial(n - 1)
print(factorial(4)) # Çıktı: 24
factorial(4) çağrısını adım adım takip edelim. Fonksiyon önce n=4 ile çalışır, base case'e uymadığı için 4 * factorial(3) ifadesini hesaplamaya çalışır ama bunun için önce factorial(3) sonucunu bilmesi gerekir. Bu yüzden factorial(3) çağrılır, o da factorial(2)'yi bekler, o da factorial(1)'i çağırır. n=1 olduğunda base case devreye girer ve fonksiyon doğrudan 1 döndürür. Artık geri sarma başlar: factorial(2) bu 1 değerini alıp 2 ile çarpar ve 2 döndürür; factorial(3) bu 2'yi alıp 3 ile çarpar ve 6 döndürür; factorial(4) ise 6'yı alıp 4 ile çarpar ve sonunda 24 sonucuna ulaşır.
Bu kodun dikkat çekici tarafı, aynı işi bir döngüyle yazsaydık gerekecek olan sayaç tutma, çarpımı bir değişkende biriktirme gibi adımların hiçbirine ihtiyaç duymamasıdır; fonksiyon problemi tanımının kendisi, çözümün de tanımı olur. Bu tür küçük ama net örnekler üzerinde bolca pratik yapmak, recursion mantığının kafanızda somutlaşmasını hızlandırır. birebir Python dersleri kapsamında bu tarz temel algoritma örnekleri, öğrencinin kendi kod yazma alışkanlığına göre adım adım işlenir; çünkü recursion'ı gerçekten öğrenmenin yolu, örneği ezberlemek değil, benzer problemleri kendi başınıza kurup denemektir.
Call Stack Nasıl İşler? Fonksiyon Çağrılarının Yığılması
Bir recursive fonksiyon çağrıldığında, bilgisayar o çağrıyı hemen bitirmez; onu bir yere "not eder" ve bir sonraki çağrıya geçer. Bu notların tutulduğu yapıya call stack (çağrı yığını) denir. Yığın kelimesi tesadüfen seçilmemiştir: tam anlamıyla üst üste dizilen tabaklar gibi çalışır. Her yeni fonksiyon çağrısı yığının en üstüne bir tabak koyar, her tamamlanan çağrı ise en üstteki tabağı alıp götürür.
Bunu zihninde canlandırmak için soyut bir açıklamadan çok, gerçek bir örnek üzerinden gitmek daha kolaydır. factorial(4) çağrısını düşünelim ve her adımda yığında ne olduğunu takip edelim:
factorial(4)çağrılır. Fonksiyon "4 taban durumu değil, banafactorial(3)sonucu gerekiyor" der ve bu sonucu beklemeye başlar. Yığınafactorial(4)eklenir.factorial(3)çağrılır. O da kendi başına sonucu bilmez,factorial(2)'yi bekler. Yığınafactorial(3)eklenir; artık yığında iki bekleyen çağrı vardır.factorial(2)çağrılır ve aynı şekildefactorial(1)'i bekleyerek yığına eklenir. Yığında üç çağrı üst üste durmaktadır.factorial(1)çağrılır. Bu, base case'e (taban duruma) ulaşılan noktadır; fonksiyon başka bir çağrı beklemeden doğrudan1değerini döndürür.- Artık geri sarma (unwinding) başlar:
factorial(2), elindeki1değerini alır, kendi2ile çarpar ve2sonucunu döndürür. Bu anda yığından bir tabak kalkar. factorial(3), gelen2sonucunu3ile çarpar,6döndürür ve yığından bir tabak daha kalkar.factorial(4), gelen6sonucunu4ile çarpar ve nihai sonuç olan24'ü döndürür; yığın tamamen boşalmış olur.
Yığının büyümesinin sebebi çok basittir: her çağrı, kendisinden bir küçüğün cevabını almadan işini bitiremediği için "beklemede" kalır ve bu bekleme yığına bir kayıt olarak eklenir. Base case'e ulaşıldığı an ise artık beklenecek bir şey kalmaz; oradan itibaren her çağrı elindeki hazır cevabı bir üsttekine iletir ve yığın adım adım küçülmeye başlar. Recursion'ı anlamanın en sağlam yolu, bu yığılma ve boşalma ritmini gözünde canlandırabilmektir.
Sonsuz Recursion (Infinite Recursion) Hatası ve Önlenmesi

Önceki bölümdeki örnekte yığın büyüyüp sonra düzenli biçimde boşaldı, çünkü her çağrı bir gün ulaşılacak bir base case'e doğru ilerliyordu. Base case hiç yazılmamışsa veya fonksiyon parametreyi taban duruma doğru küçültmeyecek şekilde çağrılıyorsa, yığın hiçbir zaman boşalmaya başlamaz; her çağrı yeni bir çağrı doğurur ve bu zincir teorik olarak sonsuza kadar sürer.
Pratikte "sonsuza kadar" sürmez, çünkü bellek sınırsız değildir. Her bekleyen çağrı yığında yer kapladığı için, base case'e ulaşılamayan bir recursion belleği hızla tüketir ve program bir noktada devam edemez. Python'da bu durum genellikle bir RecursionError mesajıyla, diğer birçok dilde ise benzer mantıkla çalışan bir "stack overflow" bildirimiyle kendini gösterir. Bu hatanın özünde karmaşık bir sistem sorunu yoktur; yığına konan tabakların hiç alınmaması, yani hiçbir çağrının "ben bitirdim" diyememesi vardır.
Bu hatayı önlemek için kontrol edilmesi gereken üç nokta vardır:
- Fonksiyonda gerçekten çalışan, ulaşılabilir bir base case tanımlanmış olmalı.
- Her recursive çağrıda, fonksiyona geçirilen parametre base case'e doğru gerçekten küçülmeli veya değişmeli; aynı değerle tekrar çağırmak zinciri asla bitirmez.
- Fonksiyon küçük ve kontrollü girdilerle (örneğin
factorial(3)gibi) test edilmeli, sonuç beklenenle örtüşüyor mu diye adım adım doğrulanmalı.
Bu tür hataları ilk defa gören biri için kafa karıştırıcı gelebilir; ancak mantığı bir kez yerleşince fark etmek de düzeltmek de hızlanır. Bu noktada tek başına ilerlemek yerine kod üzerinden birebir geri bildirim almak süreci kısaltır; birebir Python dersleri kapsamında böyle hataların canlı olarak nasıl ayıklandığını görmek, kavramı soyut bir kural olmaktan çıkarıp somut bir alışkanlığa dönüştürür.
Recursion mu İterasyon mu? Karşılaştırma Tablosu
Recursion ve iterasyon (döngü), aynı problemi çoğu zaman farklı yollardan çözebilen iki yaklaşımdır. Hangisinin tercih edileceği; problemin doğasına, performans önceliğine ve kodun ne kadar okunabilir kalması gerektiğine bağlıdır. Aşağıdaki tablo, bu seçimi yaparken dikkate alınabilecek dört temel kriteri özetler.
| Kriter | Recursion | İterasyon (Döngü) |
|---|---|---|
| Bellek kullanımı | Her çağrı call stack'te yer tutar, derinlik arttıkça bellek maliyeti büyür | Genellikle sabit bir bellek alanı kullanır, döngü sayısı belleği doğrudan büyütmez |
| Okunabilirlik | Doğası kendi kendini tekrar eden problemlerde (ağaç, alt problem) kodu sadeleştirir | Basit tekrarlı işlemlerde adımların akışını takip etmek daha doğrudan olabilir |
| Performans | Fonksiyon çağrısı ek yük getirdiği için genelde biraz daha maliyetlidir | Çağrı yükü olmadığı için çoğu durumda daha hızlı çalışır |
| Tercih edildiği problem tipi | Ağaç/graf gezme, alt problemlere bölünen yapılar, doğal olarak kendini tekrar eden tanımlar | Sabit sayıda veya basit koşullu tekrarlar, performansın öncelikli olduğu durumlar |
Genel eğilim şudur: problem kendi içinde "küçük bir kopyasına" bölünebiliyorsa recursion kodu daha anlaşılır kılar; problem düz bir tekrar ise iterasyon genellikle daha az kaynak tüketir. İkisini birbirine rakip değil, aynı algoritma kutusundaki iki farklı alet gibi görmek daha sağlıklı bir bakış açısıdır. Bu iki yaklaşımı aynı problem üzerinde karşılaştırmalı olarak yazıp çalıştırmak, aradaki farkı ezberden çok deneyerek öğrenmenin en pratik yoludur; video yazılım eğitimleri içinde bu tür karşılaştırmalı örnekleri kendi hızında izleyip tekrar tekrar incelemek mümkündür.
Recursion'ın Doğal Çözüm Sunduğu Problem Tipleri
Bazı problemler, yapıları itibarıyla kendi içinde küçük bir kopyasını barındırır. Bir ağaç yapısını düşün: bir ağacın her dalı, aslında kendi başına daha küçük bir ağaçtır. Kök düğümden başlayıp dallara indiğinde, her dalda karşına çıkan yapı bir öncekiyle aynı mantığa sahiptir — sadece daha küçük ölçektedir. Dosya sistemindeki klasör içindeki klasörler, bir organizasyon şemasındaki yöneticiye bağlı ekipler, hatta bir HTML belgesinin içindeki iç içe etiketler bile bu "kendini tekrar eden yapı" mantığına örnektir. Bu tür veri yapılarını gezmek (traversal) istediğinde, döngü ile her seviyeyi ayrı ayrı yönetmeye çalışmak hızla karmaşıklaşır; recursion ise "bu düğümü işle, sonra aynı işlemi her alt düğüm için tekrar çağır" diyerek doğal bir çözüm sunar.
Bu yaklaşımın arkasındaki genel prensibe böl ve yönet (divide and conquer) denir. Mantık şu şekilde özetlenebilir: büyük bir problemi çözmek zor geliyorsa, onu aynı problemin daha küçük versiyonlarına böl, her küçük parçayı çöz, sonra bu çözümleri birleştirip büyük problemin cevabını elde et. Sıralama algoritmalarından arama yöntemlerine kadar birçok klasik algoritma bu prensip üzerine kuruludur; recursion da bu prensibi kod düzeyinde ifade etmenin en doğal yoludur.
Fibonacci dizisi bu açıdan öğretici bir örnektir. Dizinin tanımı zaten kendini referans alır: bir sayı, kendisinden önceki iki sayının toplamıdır. Bu tanımı kodlarken "n. terimi bulmak için (n-1). ve (n-2). terimi bul" demek, problemin matematiksel tanımını satır satır koda çevirmek gibidir. İşte recursion'ın en güçlü tarafı budur: problem zaten kendini tekrar eden bir yapıya sahipse, recursive çözüm genellikle iteratif çözümden daha az kod ve daha çok okunabilirlikle aynı sonuca ulaşır.
Bu noktaya kadar öğrendiklerini pekiştirmek istersen, kendi tempoyla ilerleyebileceğin algoritma bilgi testi ile recursion, döngü mantığı ve temel algoritma kavramlarındaki seviyeni kısa sürede ölçebilirsin. Böylece hangi konuda daha çok pratik yapman gerektiğini net biçimde görürsün.
Recursion Öğrenirken Sık Yapılan Hatalar ve Pratik Öneriler
Recursion öğrenen herkesin karşılaştığı birkaç tipik hata vardır ve bunları bilmek, kendi kodunu yazarken seni büyük ölçüde rahatlatır. En sık görülen hata, base case'i unutmaktır; fonksiyon her zaman kendini çağırdığı için, durma koşulu net ve doğru yazılmazsa program bir çıkış noktası bulamaz. İkinci yaygın hata, base case yazılsa da her çağrıda parametrenin gerçekten küçülmediği durumlardır — örneğin fonksiyon kendini n ile değil de yanlışlıkla aynı n değeriyle tekrar çağırırsa, base case'e asla ulaşılmaz. Üçüncü bir hata ise fonksiyonun geri dönüş değerini doğru şekilde birleştirmemektir; ara sonuçların nasıl toplandığını (ya da çarpıldığını, birleştirildiğini) net kurmadan yazılan recursive fonksiyonlar çoğunlukla beklenmeyen sonuçlar üretir.
Bu hataları fark edebilmenin en etkili yolu, kodu bilgisayara çalıştırmadan önce kağıt üzerinde adım adım takip etmektir (dry run). Küçük bir örnek değer seç, fonksiyonun her çağrısını ve her çağrının hangi değeri döndürdüğünü sırayla yaz. Bu alıştırma, call stack'in zihninde nasıl büyüyüp küçüldüğünü görmeni sağlar ve kodu çalıştırmadan önce mantık hatalarını yakalamanı kolaylaştırır. Deneyimli geliştiriciler bile karmaşık bir recursive fonksiyonla karşılaştığında önce bu şekilde küçük bir örnek üzerinde elle takip yapar.
Öğrenme sürecinde en sağlıklı yaklaşım, küçük ve tek base case'li problemlerle başlayıp kademeli olarak zorluk seviyesini artırmaktır. Faktöriyel ve dizi toplamı gibi basit örneklerle rahat hale geldikten sonra, birden fazla recursive çağrı içeren Fibonacci gibi problemlere, ardından ağaç ve liste gibi veri yapıları üzerinde çalışan fonksiyonlara geçmek mantıklı bir sıralamadır. Her adımda "base case nedir, parametre nasıl küçülüyor, sonuçlar nasıl birleşiyor" sorularını kendine sorman, konuyu ezberlemek yerine gerçekten içselleştirmeni sağlar.
Recursion konusunu daha da derinleştirmek ve algoritmik düşünme pratiğini farklı örneklerle güçlendirmek istersen, güncel yazılım eğitimi içerikleri arasında konuyla ilgili başka anlatımlara ve alıştırmalara göz atabilirsin. Düzenli pratik, recursion'ı zamanla doğal bir düşünme aracına dönüştürür.
Sık Sorulan Sorular
Recursion nedir, en basit tanımıyla nasıl anlatılır?
Recursion, bir fonksiyonun problemi çözerken kendisini daha küçük bir versiyonuyla yeniden çağırmasıdır. Fonksiyon her çağrıldığında problemi biraz daha küçültür ve en sonunda kendiliğinden çözülebilen bir noktaya (base case) ulaşır.
Base case olmadan bir recursive fonksiyon çalışır mı?
Base case olmadan yazılan bir recursive fonksiyon kendini durduramaz ve sürekli kendini çağırmaya devam eder. Bu durum sonsuz recursion olarak bilinir ve programın bellek hatası vererek çökmesine yol açar; bu yüzden her recursive fonksiyonda en az bir base case bulunması zorunludur.
Her recursive fonksiyon iterasyonla (döngüyle) yeniden yazılabilir mi?
Evet, teorik olarak her recursive fonksiyon bir döngü ve yardımcı bir veri yapısı (örneğin bir yığın) kullanılarak iteratif şekilde yeniden yazılabilir. Ancak bazı problemlerde bu dönüşüm kodu daha karmaşık hale getirebilir; hangi yaklaşımın daha uygun olduğu probleme ve okunabilirlik önceliğine göre değişir.
Sonsuz recursion hatası nasıl önlenir?
Sonsuz recursion hatasını önlemek için her fonksiyonda net bir base case tanımlanmalı ve her recursive çağrıda parametrenin base case'e doğru gerçekten ilerlediğinden emin olunmalıdır. Kodu yazdıktan sonra küçük bir örnek üzerinde elle adım adım takip etmek bu tür hataları erken fark etmeyi sağlar.
Fibonacci hesaplamak için recursion mı yoksa döngü mü daha uygundur?
Fibonacci dizisinin tanımı kendini referans aldığı için recursive çözüm kavramsal olarak daha doğal görünür ve öğrenme aşamasında mantığı anlamak için faydalıdır. Ancak büyük terimler için performans önemliyse, döngü tabanlı veya bellekleme (memoization) uygulanmış bir yaklaşım genellikle daha verimli çalışır.
Recursion öğrenmeden önce hangi konuları bilmek gerekir?
Recursion öğrenmeden önce fonksiyon tanımlama ve fonksiyon çağırma mantığını, koşul ifadelerini (if-else) ve temel döngü kavramını rahatça kullanabiliyor olmak gerekir. Bu temeller sağlam olduğunda, recursion'ın "fonksiyonun kendini çağırması" fikri çok daha kolay oturur.
Recursion hangi tür problemlerde daha doğal bir çözüm sunar?
Recursion, ağaç ve liste gibi kendini tekrar eden veri yapılarında, böl ve yönet mantığıyla çözülen problemlerde ve matematiksel tanımı zaten kendine referans veren problemlerde (faktöriyel, Fibonacci gibi) genellikle daha doğal ve okunabilir bir çözüm sunar.
Recursion, ilk bakışta soyut görünse de temelinde basit ve tekrarlanabilir bir mantık taşır: problemi küçült, küçük parçayı çöz, sonuçları birleştir. Bu mantığı küçük örneklerle adım adım pekiştirdikten sonra, algoritmik düşünme becerini bir üst seviyeye taşımak istersen 1-1 özel Python dersleri ile konuyu bire bir bir eğitmen eşliğinde ilerletebilirsin.