Yazılım mühendisliği kariyerimde ilerlerken, pek çok dönüm noktasından geçtim. Bunlardan biri, ve belki de en stresli olanları, şüphesiz teknik mülakatlardı. İlk mülakatlarımdan birinde, heyecandan bildiğim bir soruyu bile doğru düzgün yanıtlayamadığımı hatırlıyorum. Ter dökmüş, kekelemiş ve o an yerin dibine girmek istemiştim.
Ancak bu deneyimler, bana çok değerli bir ders verdi: Teknik mülakatlara hazırlanmak şarttı. Sadece teorik bilgiye sahip olmak yetmiyor, aynı zamanda bu bilgiyi pratik problem çözme becerileriyle birleştirmek ve etkili bir şekilde sunmak gerekiyordu.
Yıllar içinde, sayısız mülakata girdim, çıktım. Kimi zaman başarılı oldum, kimi zaman başarısız. Ama her seferinde, neyi daha iyi yapabileceğimi öğrendim. Bu süreçte, mülakatlarda sıkça karşılaşılan soru türlerini, en etkili çözüm yöntemlerini ve mülakat sırasında dikkat edilmesi gereken önemli noktaları not aldım.
İşte bu rehber, benim bu kişisel deneyimlerimin ve uzun yıllara yayılan araştırmalarımın bir ürünü. Yazılım mülakatlarında karşınıza çıkabilecek temel zorlukları aşmanıza yardımcı olacak pratik bilgileri, C# diliyle örnekleyerek sunuyorum. Her bölümde, belirli bir konuya odaklanacak, örnek soruları adım adım çözecek ve mülakatlarda nasıl daha başarılı olabileceğinize dair ipuçları paylaşacağım.
Bu rehberin, yazılım kariyerinizde size yol göstermesini ve hayalinizdeki işe bir adım daha yaklaşmanızı sağlamasını umuyorum. Unutmayın, pratik yapmak ve kendinize güvenmek, başarının anahtarıdır. Şimdi, mülakatlara hazırlık yolculuğumuza başlayalım!
1. Temel Programlama ve Algoritma Soruları
Bu bölümde, temel programlama kavramlarını ve algoritmik düşünme yeteneğinizi test eden sorulara odaklanacağız.
Soru 1: FizzBuzz Problemi
Açıklama: Mülakatlarda karşıma çıkan, hatta artık bir klasik haline gelmiş bu soru, aslında temel programlama yeteneklerini ölçmek için harika bir araç. Benden istenen şuydu: 1’den 100’e kadar olan sayıları ekrana yazdıracaktım. Ancak bir şart vardı; 3’ün katı olan sayılar yerine “Fizz”, 5’in katı olanlar yerine “Buzz” yazmam gerekiyordu. Eğer bir sayı hem 3’ün hem de 5’in katıysa, o zaman da “FizzBuzz” yazdıracaktım. Geriye kalan sayılar içinse, sayının kendisini yazdırmam yeterliydi. İlk başta basit gibi görünse de, aslında dikkatli olmazsam kolayca hata yapabileceğim bir soruydu.
C# Çözümü:
public static void FizzBuzz()
{
for (int i = 1; i <= 100; i++)
{
if (i % 3 == 0 && i % 5 == 0)
{
Console.WriteLine("FizzBuzz");
}
else if (i % 3 == 0)
{
Console.WriteLine("Fizz");
}
else if (i % 5 == 0)
{
Console.WriteLine("Buzz");
}
else
{
Console.WriteLine(i);
}
}
}
Çözümün Açıklaması:
- for döngüsü 1’den 100’e kadar olan sayıları dolaşır.
- if koşulları, sayının 3, 5 veya her ikisine birden bölünüp bölünmediğini kontrol eder.
- Uygun durumlarda “Fizz”, “Buzz” veya “FizzBuzz” yazdırılır.
- Diğer durumlarda, sayının kendisi yazdırılır.
Soru 2: Palindrom Kontrolü
Açıklama: Mülakatlarda sıkça karşıma çıkan bir diğer soru da palindrom kontrolüydü. Bu sefer, verilen bir string’in palindrom olup olmadığını kontrol eden bir fonksiyon yazmam gerekiyordu. Palindrom, tersten okunduğunda da aynı olan kelime veya cümlelere deniyor. Örneğin, “ada”, “kabak” veya “ey edip adanada pide ye” gibi. Mülakatçı, benden bu kontrolü yapan, hızlı ve hatasız çalışan bir kod yazmamı bekliyordu. Hatta bir keresinde, mülakatçı işi biraz daha zorlaştırıp, boşlukları ve noktalama işaretlerini de göz ardı etmemi istemişti!
C# Çözümü: (Kod bloğu ve çözüm açıklaması আগের yanıtlardaki gibi kalabilir, ancak aşağıdaki gibi küçük bir ekleme yapılabilir.)
public static bool IsPalindrome(string text)
{
string cleanText = new string(text.Where(c => char.IsLetterOrDigit(c)).ToArray()).ToLower();
int left = 0;
int right = cleanText.Length - 1;
while (left < right)
{
if (cleanText[left] != cleanText[right])
{
return false;
}
left++;
right--;
}
return true;
}
Çözümün Açıklaması:
Temizleme: new string(text.Where(c => char.IsLetterOrDigit(c)).ToArray()).ToLower(); Bu kısım, mülakatlarda bazen özellikle istenen bir detay. Boşlukları ve noktalama işaretlerini kaldırıp, tüm harfleri küçük harfe çevirerek, “Ey Edip Adana’da pide ye!” gibi bir cümlenin de palindrom olarak kabul edilmesini sağlıyoruz.
- İki İşaretçi: left ve right adında iki tane işaretçi (pointer) tanımlıyoruz.
- Karşılaştırma Döngüsü: while döngüsü ile karakterleri karşılaştırıyoruz.
- İşaretçileri Güncelleme: Karakterler aynıysa, işaretçileri güncelliyoruz.
- Döngü Sonu: Döngü bittiğinde metin palindromdur.
Soru 3: Fibonacci Serisi
Açıklama: Fibonacci serisinin n. elemanını hesaplayan bir fonksiyon yazın (her sayı kendisinden önceki iki sayının toplamıdır: 0, 1, 1, 2, 3, 5, 8…).
C# Çözümü (Recursive):
public static int FibonacciRecursive(int n)
{
if (n <= 1)
{
return n;
}
return FibonacciRecursive(n - 1) + FibonacciRecursive(n - 2);
}
Açıklama: Özyinelemeli (recursive) çözümde, temel durum n 0 veya 1 olduğunda fonksiyon n’i geri döndürür. Aksi takdirde, fonksiyon kendisini iki kez çağırarak Fibonacci serisinin önceki iki elemanının toplamını döndürür.
C# Çözümü (Iterative):
public static int FibonacciIterative(int n)
{
if (n <= 1) return n;
int a = 0, b = 1, result = 0;
for (int i = 2; i <= n; i++)
{
result = a + b;
a = b;
b = result;
}
return result;
}
Açıklama: Iteratif çözümde, Fibonacci serisinin her bir elemanı döngü ile hesaplanır. Bu çözüm, daha verimli ve hafıza dostudur.
Soru 4: En Büyük Ortak Bölen (EBOB)
Açıklama: İki sayının en büyük ortak bölenini (EBOB) bulan bir fonksiyon yazın.
C# Çözümü (Euclid Algoritması):
public static int GCD(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
Açıklama: Euclid algoritması kullanılarak iki sayının EBOB’u hesaplanır. Bu algoritma, sayılardan birinin sıfır olana kadar işlemi tekrarlar.
2. Veri Yapıları Soruları
Bu bölüm, veri yapılarını kullanma becerinizi test eden soruları içermektedir.
Soru 1: Array ve LinkedList Farkı
Açıklama: C#’da Array ve LinkedList<T> arasındaki farkları açıklayın. Performans, bellek kullanımı ve kullanım senaryoları açısından karşılaştırın.
Cevap:
- Array (Dizi): Sabit boyutlu, bellekte ardışık olarak yer kaplar. Elemanlara indeks numarası ile erişim sağlar (O(1) zaman). Araya eleman ekleme/silme işlemleri yavaş olabilir (O(n)).
- LinkedList (Bağlı Liste): Dinamik boyutlu, bellekte dağınık olarak yer kaplar. Elemanlara sırasıyla erişim sağlanır (O(n) zaman). Araya eleman ekleme/silme işlemleri hızlıdır (O(1)).
Soru 2: Stack ve Queue
Açıklama: Stack<T> ve Queue<T> veri yapılarını açıklayın. C# ile birer örnek vererek kullanımını gösterin.
Cevap:
- Stack (Yığın): LIFO (Last-In, First-Out) prensibiyle çalışır. Push() metodu ile eleman eklenir, Pop() ile çıkarılır.
Stack<string> stack = new Stack<string>();
stack.Push("A");
stack.Push("B");
stack.Push("C");
Console.WriteLine(stack.Pop()); // C
- Queue (Kuyruk): FIFO (First-In, First-Out) prensibiyle çalışır. Enqueue() ile eleman eklenir, Dequeue() ile çıkarılır.
Queue<int> queue = new Queue<int>(); queue.Enqueue(1); queue.Enqueue(2); queue.Enqueue(3); Console.WriteLine(queue.Dequeue()); // 1
Soru 3: HashTable ve HashMap (Dictionary)
- Açıklama: C#’da Hashtable ve Dictionary<TKey, TValue> arasındaki farkları açıklayın. Hangisini ne zaman tercih edersiniz?
- Hashtable: Non-generic: object türünde anahtar ve değerleri saklar. Bu, boxing/unboxing işlemlerine neden olabilir (performans kaybı).
- Thread-safe: Birden fazla thread tarafından güvenli bir şekilde kullanılabilir (ancak bu da performansı düşürebilir). Eski: .NET Framework’ün ilk sürümlerinden kalmadır.
- Dictionary<TKey, TValue>: Generic: Belirli bir türdeki (TKey, TValue) anahtar ve değerleri saklar. Boxing/unboxing işlemleri olmaz (daha iyi performans).
- Not thread-safe: Birden fazla thread tarafından aynı anda kullanılması güvenli değildir (senkronizasyon mekanizmaları kullanılmalıdır).
- Modern: .NET Framework 2.0’dan beri kullanılmaktadır. Hashtable’a göre daha modern ve performanslı bir seçenektir.
Ne Zaman Hangisini Kullanmalı?
- Dictionary<TKey, TValue>: Tip güvenliği (type safety) ve performansın önemli olduğu çoğu durumda tercih edilir. Modern C# uygulamalarında Hashtable yerine Dictionary kullanılması önerilir.
- Hashtable: Yalnızca çok eski .NET Framework sürümleriyle uyumluluk gerekiyorsa veya birden fazla thread’in aynı anda erişimini yönetmek için yerleşik thread-safe yapısı gerekiyorsa kullanılabilir. Ancak, thread-safe kullanım için bile ConcurrentDictionary<TKey, TValue> daha iyi bir performans sunar.
Soru 4: Binary Search Tree (İkili Arama Ağacı)
- Açıklama: Bir Binary Search Tree (BST)’de bir elemanın varlığını nasıl kontrol edersiniz? C# ile bir Node sınıfı ve Contains metodu yazarak gösterin.
- C# Çözümü:
public class Node
{
public int Data;
public Node Left;
public Node Right;
public Node(int data)
{
Data = data;
Left = null;
Right = null;
}
}
public class BinarySearchTree
{
public Node Root;
public BinarySearchTree()
{
Root = null;
}
public bool Contains(int data)
{
return ContainsRecursive(Root, data);
}
private bool ContainsRecursive(Node current, int data)
{
if (current == null)
{
return false;
}
if (data == current.Data)
{
return true;
}
else if (data < current.Data)
{
return ContainsRecursive(current.Left, data);
}
else
{
return ContainsRecursive(current.Right, data);
}
}
// İsteğe bağlı: Eleman ekleme metodu (Insert)
public void Insert(int data)
{
Root = InsertRecursive(Root, data);
}
private Node InsertRecursive(Node current, int data)
{
if (current == null)
{
return new Node(data);
}
if (data < current.Data)
{
current.Left = InsertRecursive(current.Left, data);
}
else if (data > current.Data)
{
current.Right = InsertRecursive(current.Right, data);
}
// Eğer data zaten ağaçta varsa, bir şey yapmaya gerek yok (duplicate'e izin vermiyoruz)
return current;
}
Çözümün Açıklaması:
- Node Sınıfı: Her düğümün (node) veriyi (Data), sol alt ağacı (Left) ve sağ alt ağacı (Right) tuttuğu bir Node sınıfı tanımlanır.
- BinarySearchTree Sınıfı: Ağacın kökünü (Root) tutan bir BinarySearchTree sınıfı tanımlanır.
- Contains Metodu (Public): Bu metod, kullanıcıya ağaçta bir elemanın olup olmadığını sorgulama imkanı sunar. ContainsRecursive metodunu çağırarak arama işlemini başlatır.
- ContainsRecursive Metodu (Private):
- Temel Durum (Base Case): Eğer current düğümü null ise, aranan eleman ağaçta yoktur ve false döndürülür.
- Eşitlik Kontrolü: Eğer data, current düğümünün verisine (current.Data) eşitse, aranan eleman bulunmuştur ve true döndürülür.
- Sol Alt Ağaca Git: Eğer data, current düğümünün verisinden küçükse, arama işlemi sol alt ağaçta (current.Left) özyinelemeli olarak devam eder (ContainsRecursive(current.Left, data)). Sağ
- Alt Ağaca Git: Eğer data, current düğümünün verisinden büyükse, arama işlemi sağ alt ağaçta (current.Right) özyinelemeli olarak devam eder (ContainsRecursive(current.Right, data)).
- (İsteğe Bağlı) Insert Metodu: Bu bölüm, Binary Search Tree’ye nasıl eleman ekleneceğini gösterir. Mülakatlarda bazen ekleme işlemi de sorulabilir. Insert metodu, ağaca yeni bir veri eklemek için kullanılır. InsertRecursive metodu, özyinelemeli olarak doğru yeri bulur ve yeni düğümü oluşturur. Eğer eklenecek veri zaten ağaçta varsa, bu örnekte herhangi bir işlem yapılmaz (çift kayıtlara izin verilmez).
Kullanım Örneği (Binary Search Tree):
BinarySearchTree bst = new BinarySearchTree(); bst.Insert(50); bst.Insert(30); bst.Insert(20); bst.Insert(40); bst.Insert(70); bst.Insert(60); bst.Insert(80); Console.WriteLine(bst.Contains(40)); // Çıktı: True Console.WriteLine(bst.Contains(90)); // Çıktı: False
3. Sistem Tasarımı Soruları
Bu bölüm, daha büyük ölçekli sistemlerin nasıl tasarlanacağına dair sorular içerir. Bu sorular genellikle daha deneyimli adaylara yöneliktir. C# ile doğrudan kod yazmaktan ziyade, sistem mimarisi, bileşenler, veri akışı ve ölçeklenebilirlik gibi konular tartışılır.
- Soru 1: TinyURL Tasarımı
- Açıklama: TinyURL gibi bir URL kısaltma servisi nasıl tasarlarsınız? Temel bileşenleri, veri modelini, kısaltma algoritmasını ve ölçeklenebilirliği açıklayın.
- Cevap:
- Temel Gereksinimler:
- Kısaltma: Uzun bir URL’yi kısa bir URL’ye dönüştürme.
- Yönlendirme: Kısa URL’ye tıklandığında, orijinal uzun URL’ye yönlendirme.
- Ölçeklenebilirlik: Milyonlarca URL’yi işleyebilme.
- Yüksek Erişilebilirlik (High Availability): Servisin sürekli çalışır durumda olması.
- Özelleştirme (Opsiyonel): Kullanıcının kısa URL’yi özelleştirebilmesi (örneğin, anlamlı bir kelime kullanabilmesi).
Bileşenler:
Web Sunucusu (API Gateway): Kullanıcılardan gelen istekleri (URL kısaltma ve yönlendirme) kabul eden ve işleyen ön yüz (örneğin, ASP.NET Core ile geliştirilmiş bir REST API).
- Uygulama Sunucusu (Application Server): Kısaltma mantığını ve veritabanı etkileşimlerini yöneten ana iş mantığı katmanı (örneğin, C# ile yazılmış bir servis).
- Veritabanı: Uzun ve kısa URL’leri eşleştiren verileri saklayan veritabanı (örneğin, SQL Server, PostgreSQL, NoSQL veritabanları – Redis, Cassandra).
- Cache: Sık kullanılan URL eşleştirmelerini önbelleğe alarak performansı artıran bir önbellek katmanı (örneğin, Redis, Memcached).
Veri Modeli:
URL_Mapping (Table) --------------------- - id (BIGINT, Primary Key, Auto-Increment) // Benzersiz ID - long_url (VARCHAR) // Uzun URL - short_url (VARCHAR) // Kısa URL (örneğin, "aB12cD") - creation_date (DATETIME) // Oluşturulma tarihi (isteğe bağlı) - expiration_date (DATETIME) // Bitiş tarihi (isteğe bağlı, premium özellik) - user_id (INT) // Kullanıcı ID'si (isteğe bağlı, üyelik sistemi varsa)
Kısaltma Algoritması:
Base62 Encoding: En yaygın yöntemlerden biri. [0-9a-zA-Z] karakterlerini (toplam 62 karakter) kullanarak kısa URL’ler oluşturur.
- Her yeni URL için veritabanında bir id (auto-increment) oluşturulur.
- Bu id değeri Base62’ye çevrilir. Örneğin, 125 (decimal) -> “1Z” (Base62).
- Bu Base62 değeri, kısa URL olarak kullanılır.
// Basit Base62 Encoding Örneği (daha gelişmiş algoritmalar kullanılabilir)
public static string EncodeBase62(long id)
{
const string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuilder shortUrl = new StringBuilder();
while (id > 0)
{
int remainder = (int)(id % 62);
shortUrl.Insert(0, chars[remainder]);
id /= 62;
}
return shortUrl.ToString();
}
public static long DecodeBase62(string shortUrl)
{
const string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
long id = 0;
long power = 1;
for(int i = shortUrl.Length - 1; i>=0; i--)
{
int digit = chars.IndexOf(shortUrl[i]);
id += digit * power;
power *= 62;
}
return id;
}
Hash Fonksiyonları: MD5, SHA-256 gibi hash fonksiyonları kullanılabilir, ancak çakışma (collision) olasılığına dikkat edilmelidir. Çakışma durumunda, farklı bir kısaltma yöntemi denenmelidir (örneğin, hash’e bir sayı ekleyerek tekrar hash’lemek).
Ölçeklenebilirlik:
- Yatay Ölçekleme (Horizontal Scaling): Uygulama sunucularının sayısını artırarak yükü dağıtmak.
- Veritabanı Sharding: Veritabanını birden fazla sunucuya bölerek (sharding) performansı artırmak.
- Caching: Sık erişilen URL eşleştirmelerini önbellekte tutarak veritabanı yükünü azaltmak.
- Load Balancing: Gelen istekleri birden fazla sunucu arasında dağıtmak (örneğin, Nginx, HAProxy).
Akış:
Kısaltma:
- Kullanıcı uzun URL’yi gönderir.
- Web sunucusu isteği uygulama sunucusuna iletir.
- Uygulama sunucusu, veritabanında uzun URL’nin zaten kısaltılmış olup olmadığını kontrol eder.
- Eğer kısaltılmışsa, mevcut kısa URL döndürülür.
- Eğer kısaltılmamışsa, yeni bir id oluşturulur ve Base62 ile kodlanarak kısa URL elde edilir.
- Uzun URL ve kısa URL veritabanına kaydedilir.
- Kısa URL kullanıcıya döndürülür.
Yönlendirme:
- Kullanıcı kısa URL’ye tıklar.
- Web sunucusu isteği uygulama sunucusuna iletir.
- Uygulama sunucusu, kısa URL’yi veritabanında arar.
- Eğer bulunursa, ilgili uzun URL alınır.
- Önbellek (cache) kontrol edilir. Eğer uzun URL önbellekteyse, doğrudan oradan alınır ve 301 (Moved Permanently) yönlendirmesi yapılır.
- Eğer önbellekte yoksa, veritabanından uzun URL alınır.
- Web sunucusu, kullanıcıyı 301 yönlendirmesi ile uzun URL’ye yönlendirir.
- Uzun URL ve kısa URL eşleştirmesi önbelleğe eklenir.
Soru 2: Twitter Feed Tasarımı (Haber Akışı)
- Açıklama: Twitter’ın feed (haber akışı) sistemi nasıl çalışır? Nasıl tasarlarsınız? Ölçeklenebilirlik, gerçek zamanlı güncellemeler ve kişiselleştirme gibi konulara değinin.
- Cevap: (Bu soru, sistem tasarımı konusunda daha derinlemesine bilgi gerektirir ve mülakatlarda genellikle daha üst düzey pozisyonlar için sorulur.)
- Temel Gereksinimler
- Tweet Oluşturma: Kullanıcıların tweet atabilmesi
- Takip Etme: Kullanıcıların başka kullanıcıları takip edebilmesi.
- Haber Akışı Oluşturma: Kullanıcının takip ettiği kişilerin tweet’lerinin kronolojik sırada gösterilmesi.
- Gerçek Zamanlı Güncellemeler: Yeni tweet’lerin akışta hemen görünmesi.
- Ölçeklenebilirlik: Milyonlarca kullanıcı ve tweet’i destekleyebilmesi.
- Kişiselleştirme: Kullanıcının ilgi alanlarına göre tweet’lerin sıralanması (opsiyonel, daha ileri seviye).
Bileşenler:
- API Gateway: Kullanıcılardan gelen istekleri (tweet atma, takip etme, akışı görüntüleme) kabul eden ve işleyen ön yüz.
- Tweet Servisi: Tweet oluşturma, silme ve tweet meta verilerini (yazar, zaman damgası, içerik vb.) yönetme.
- Takipçi (Follower) Servisi: Kullanıcıların takip ilişkilerini yönetme.
- Haber Akışı (Feed) Servisi: Her kullanıcı için haber akışını oluşturma ve yönetme.
- Veritabanları: Tweet Veritabanı: Tweet’leri saklama (örneğin, Cassandra, HBase gibi NoSQL veritabanları). Takipçi Veritabanı: Takip ilişkilerini saklama (örneğin, graph veritabanı – Neo4j, veya ilişkisel veritabanı). Haber Akışı Veritabanı/Önbelleği: Her kullanıcının haber akışını önceden hesaplanmış olarak saklama (örneğin, Redis).
- Cache: Sık erişilen verileri (örneğin, popüler tweet’ler, sık görüntülenen kullanıcı profilleri) önbelleğe alma.
- Mesaj Kuyruğu (Message Queue): Asenkron işlemler (örneğin, yeni tweet’in tüm takipçilerin akışına eklenmesi) için (örneğin, Kafka, RabbitMQ).
- (Opsiyonel) Öneri Sistemi (Recommendation Engine): Kullanıcıya ilgi alanlarına göre tweet’ler önerme.
Veri Modeli:
``` Tweet (Table) --------------------- - tweet_id (BIGINT, Primary Key, Auto-Increment) - user_id (BIGINT) // Tweet'i atan kullanıcının ID'si - content (TEXT) // Tweet içeriği - created_at (DATETIME) // Tweet'in atıldığı zaman - ... (diğer meta veriler - resim, video linkleri vb.) Follower (Table) - Takip İlişkileri --------------------- - follower_id (BIGINT) // Takip eden kullanıcının ID'si - followee_id (BIGINT) // Takip edilen kullanıcının ID'si - created_at (DATETIME) // Takip etme zamanı Feed (Table/Cache) - Önceden Oluşturulmuş Haber Akışları --------------------- - user_id (BIGINT) // Haber akışının sahibi olan kullanıcı - tweet_id (BIGINT) // Tweet ID'si - created_at (DATETIME) // Tweet'in atıldığı zaman (sıralama için) ```
- Akış:
- Tweet Atma:
- Kullanıcı tweet atar.
- API Gateway isteği Tweet Servisi’ne iletir.
- Tweet Servisi, tweet’i Tweet Veritabanı’na kaydeder.
- Tweet Servisi, yeni tweet bilgisini bir mesaj kuyruğuna (örneğin, Kafka) gönderir. Bu mesaj, tweet’in ID’sini ve tweet’i atan kullanıcının ID’sini içerir.
- Mesaj kuyruğundaki mesaj, Haber Akışı Servisi tarafından asenkron olarak işlenir.
Haber Akışı Oluşturma (Asenkron İşlem):
- Haber Akışı Servisi, mesaj kuyruğundan yeni tweet bilgisini alır.
- Takipçi Servisi’ni kullanarak, tweet’i atan kullanıcının tüm takipçilerini bulur.
- Her bir takipçinin haber akışını (önceden hesaplanmış ve önbellekte/veritabanında saklanan) günceller. Bu güncelleme, yeni tweet’i akışın başına eklemeyi içerir.
- Fan-out on Write (Yazmada Yayma): Yeni bir tweet atıldığında, bu tweet hemen tüm takipçilerin haber akışlarına yazılır. Bu, okuma işlemini (haber akışını görüntülemeyi) çok hızlı yapar, ancak yazma işlemi daha yavaştır (çünkü potansiyel olarak milyonlarca akışı güncellemek gerekebilir).
- Fan-out on Read (Okumada Yayma): Yeni bir tweet atıldığında, sadece tweet’in bilgisi kaydedilir. Bir kullanıcı haber akışını görüntülemek istediğinde, o kullanıcının takip ettiği kişilerin son tweet’leri o anda birleştirilerek akış oluşturulur. Bu, yazma işlemini hızlı yapar, ancak okuma işlemi daha yavaştır (çünkü akışın her seferinde oluşturulması gerekir). Twitter, hibrit bir yaklaşım kullanır (popüler kullanıcılar için fan-out on write, daha az takipçisi olanlar için fan-out on read).
Haber Akışını Görüntüleme:
- Kullanıcı haber akışını görüntülemek istediğinde, API Gateway isteği Haber Akışı Servisi’ne iletir.
- Haber Akışı Servisi, kullanıcının önceden hesaplanmış haber akışını önbellekten (örneğin, Redis) veya veritabanından (eğer önbellekte yoksa) getirir.
- Haber akışı, kullanıcıya kronolojik sırada (veya kişiselleştirilmiş bir algoritmaya göre) sunulur.
Ölçeklenebilirlik:
- Yatay Ölçekleme: API Gateway, Tweet Servisi, Takipçi Servisi ve Haber Akışı Servisi’nin birden fazla örneği (instance) çalıştırılarak yük dağıtılır.
- Veritabanı Sharding: Tweet Veritabanı ve Takipçi Veritabanı, birden fazla sunucuya bölünerek (sharding) veri yükü dağıtılır. Sharding, kullanıcı ID’sine veya tweet ID’sine göre yapılabilir.
- Caching: Redis gibi bir önbellek sistemi kullanılarak sık erişilen veriler (popüler tweet’ler, kullanıcı profilleri, haber akışları) önbelleğe alınır. Bu, veritabanı üzerindeki yükü azaltır ve performansı artırır.
- Asenkron İşlemler: Mesaj kuyrukları (Kafka, RabbitMQ) kullanılarak zaman alan işlemler (örneğin, tüm takipçilerin haber akışlarını güncelleme) asenkron olarak arka planda yapılır. Bu, kullanıcı arayüzünün donmasını engeller.
- Coğrafi Dağıtım: Veri merkezleri farklı coğrafi konumlara dağıtılarak kullanıcılara daha yakın sunuculardan hizmet verilir. Bu, gecikmeyi azaltır ve erişilebilirliği artırır.
Gerçek Zamanlı Güncellemeler:
- WebSockets: Sunucu ile istemci (tarayıcı) arasında sürekli açık bir bağlantı kurularak gerçek zamanlı güncellemeler sağlanabilir.
- Server-Sent Events (SSE): Sunucudan istemciye tek yönlü gerçek zamanlı güncellemeler göndermek için kullanılabilir.
- Long Polling: İstemci, sunucuya bir istek gönderir ve sunucu, yeni bir güncelleme olana kadar bağlantıyı açık tutar.
Kişiselleştirme:
Öneri Sistemleri (Recommendation Engines): Kullanıcının takip ettiği kişiler, beğendiği tweet’ler, etkileşimde bulunduğu konular ve diğer verilere dayanarak kişiselleştirilmiş bir haber akışı oluşturulabilir. Makine öğrenimi algoritmaları kullanılarak kullanıcıya daha ilgili içerikler gösterilebilir.
Programlama Dili ve Framework Soruları
Bu bölümde, C# ve .NET ekosistemiyle ilgili özel sorulara odaklanılacak.
- Soru 1: Java’da Garbage Collection Nasıl Çalışır? (C# için Uyarlama)
Açıklama: C#’ta Garbage Collection (Çöp Toplama) nasıl çalışır? Hangi algoritmalar kullanılır? Ne zaman tetiklenir? Performansa etkisi nedir?
Cevap:
Çöp Toplama (Garbage Collection – GC): C#’ta (ve .NET’te genel olarak), yönetilen bellekte (managed heap) artık kullanılmayan nesneleri otomatik olarak bulan ve temizleyen bir mekanizmadır. Geliştiricinin bellek yönetimiyle (bellek ayırma ve serbest bırakma) doğrudan ilgilenmesine gerek kalmaz.
Nasıl Çalışır?
- Mark (İşaretleme): GC, uygulamanın kök nesnelerinden (root objects) başlayarak (örneğin, statik değişkenler, yerel değişkenler, CPU kayıtçılarındaki referanslar) canlı (ulaşılabilir) nesneleri izler. Ulaşılabilen her nesne “işaretlenir”.
- Sweep (Süpürme): İşaretlenmemiş nesneler (artık kullanılmayan nesneler) belirlenir ve bu nesnelerin kullandığı bellek alanları serbest bırakılır.
- Compact (Sıkıştırma – Opsiyonel): Serbest bırakılan bellek alanları arasındaki boşlukları doldurmak ve belleği daha verimli kullanmak için canlı nesneler bellekte bir araya toplanır (compact edilir). Bu, bellek parçalanmasını (fragmentation) azaltır.
Kullanılan Algoritmalar:
- .NET GC, generational (nesilsel) bir çöp toplayıcıdır. Nesneler, “nesillere” (generations) ayrılır:
- Gen 0: Yeni oluşturulan nesneler. En sık çöp toplama burada yapılır.
- Gen 1: Gen 0’daki bir çöp toplama işleminden sonra hayatta kalan nesneler.
- Gen 2: Gen 1’deki bir çöp toplama işleminden sonra hayatta kalan nesneler. En seyrek çöp toplama burada yapılır.
- Temel olarak mark-and-sweep ve mark-and-compact algoritmalarının bir kombinasyonu kullanılır. Nesilsel yaklaşım, çöp toplama işleminin daha sık ve daha kısa süreli olmasını sağlayarak uygulamanın tepki süresini iyileştirir.
Ne Zaman Tetiklenir?
Otomatik Tetikleyiciler:
- Gen 0’da bellek dolduğunda.
- Sistem belleği azaldığında.
- Büyük nesne yığını (Large Object Heap – LOH) dolduğunda.
- Manuel Tetikleme: GC.Collect() metodu ile manuel olarak tetiklenebilir (genellikle önerilmez, GC’nin kendi optimizasyonlarına güvenmek daha iyidir).
Performansa Etkisi:
Artıları:
- Bellek sızıntılarını (memory leaks) önler.
- Geliştiriciyi bellek yönetimi yükünden kurtarır.
- Bellek parçalanmasını azaltır.
Eksileri:
- Çöp toplama işlemi, uygulamanın kısa süreli duraklamalarına neden olabilir (“stop-the-world” pauses). Bu, özellikle gerçek zamanlı uygulamalar için sorun olabilir.
- Çöp toplama işleminin ne zaman gerçekleşeceğini kesin olarak kontrol etmek mümkün değildir.
Soru 2: Python’da List Comprehension Nedir? (C# için Uyarlama)
Açıklama: C#’ta LINQ (Language Integrated Query) kullanarak list comprehension benzeri işlemler nasıl yapılır? Bir örnek verin.
Cevap:
- C#’ta Python’daki list comprehension’a en yakın yapı LINQ’tur. LINQ, koleksiyonlar (listeler, diziler, vb.) üzerinde sorgulama ve dönüşüm işlemleri yapmayı sağlayan güçlü bir araçtır.
- Örnek: Diyelim ki bir sayı listemiz var ve bu listedeki çift sayıların karesini almak istiyoruz.
// Sayı listesi
List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// LINQ ile çift sayıların karesini alma
var squaredEvenNumbers = numbers
.Where(n => n % 2 == 0) // Çift sayıları filtrele
.Select(n => n * n); // Her sayının karesini al
// Sonucu yazdırma
foreach (var num in squaredEvenNumbers)
{
Console.WriteLine(num);
}
// Çıktı:
// 4
// 16
// 36
// 64
// 100
// Alternatif: Query Syntax
var squaredEvenNumbers2 = from n in numbers
where n % 2 == 0
select n*n;
Açıklama:
- Where(n => n % 2 == 0): Bu, bir lambda ifadesi kullanır. n, listedeki her bir sayıyı temsil eder. n % 2 == 0 koşulu, sayının çift olup olmadığını kontrol eder. Where metodu, bu koşulu sağlayan elemanları filtreler.
- Select(n => n * n): Bu da bir lambda ifadesi kullanır. n, filtrelenmiş listedeki her bir sayıyı temsil eder. n * n ifadesi, her sayının karesini hesaplar. Select metodu, her eleman üzerinde bu dönüşümü uygular.
- from n in numbers where n%2 == 0 select n*n; Query söz dizimi, bazı geliştiriciler tarafından daha okunaklı bulunur, ancak method söz dizimi genellikle daha esnektir ve daha fazla LINQ operatörünü destekler.
Soru 3: JavaScript’te Closure Nedir? (C# için Uyarlama)
Açıklama: C#’ta closure (kapanış) nedir? Nasıl çalışır? Bir örnekle açıklayın.
Cevap:
- Closure (Kapanış): Bir fonksiyonun, tanımlandığı kapsamın (lexical scope) dışındaki değişkenlere erişebilme yeteneğidir. Başka bir deyişle, içteki bir fonksiyon, dıştaki fonksiyonun değişkenlerine (parametreler dahil), dıştaki fonksiyonun çalışması bitmiş olsa bile erişebilir.
- C#’ta Nasıl Çalışır? C#’ta, lambda ifadeleri ve anonim metotlar closure oluşturabilir. Dıştaki fonksiyonun değişkenleri, içteki fonksiyon tarafından “yakalanır” (captured) ve içteki fonksiyonun bir parçası haline gelir.
public static Func<int> CreateCounter(int initialValue)
{
int count = initialValue; // Dış fonksiyonun değişkeni
Func<int> counter = () => // Lambda ifadesi (iç fonksiyon)
{
count++; // Dış fonksiyonun değişkenine erişim (closure)
return count;
};
return counter;
}
// Kullanım
Func<int> myCounter = CreateCounter(10);
Console.WriteLine(myCounter()); // Çıktı: 11
Console.WriteLine(myCounter()); // Çıktı: 12
Console.WriteLine(myCounter()); // Çıktı: 13
Açıklama:
- CreateCounter fonksiyonu, bir initialValue parametresi alır ve bir count değişkeni tanımlar.
- İçeride bir lambda ifadesi (counter) tanımlanır. Bu lambda ifadesi, count değişkenini artırır ve döndürür.
- CreateCounter fonksiyonu, counter lambda ifadesini (bir Func<int>) döndürür.
- myCounter değişkenine CreateCounter(10) çağrısının sonucu atanır. myCounter artık bir closure’dur.
- myCounter() her çağrıldığında, count değişkeni (başlangıçta 10 olan) artırılır ve yeni değeri döndürülür. CreateCounter fonksiyonunun çalışması bitmiş olmasına rağmen, count değişkeni myCounter closure’u tarafından “hatırlanır” ve değiştirilebilir.
Soru 4: C#’ta async/await Nasıl Çalışır?
Açıklama: C#’da async ve await anahtar kelimeleri nasıl çalışır? Senkron ve asenkron programlama arasındaki fark nedir? Bir örnek verin.
Cevap:
- Senkron Programlama: Kod, satır satır, sırayla çalışır. Bir işlem bitmeden diğerine geçilmez. Uzun süren işlemler (örneğin, ağ isteği, dosya okuma/yazma), kullanıcı arayüzünün donmasına neden olabilir.
- Asenkron Programlama: Uzun süren işlemler, ana iş parçacığını (main thread) bloke etmeden arka planda çalıştırılır. Bu sayede, kullanıcı arayüzü donmaz ve uygulama tepkisel (responsive) kalır. async ve await anahtar kelimeleri, C#’ta asenkron programlamayı kolaylaştırmak için kullanılır.
- async Anahtar Kelimesi: Bir metodun asenkron olduğunu belirtmek için kullanılır. async ile işaretlenen bir metot, await anahtar kelimesini içerebilir. async metotlar genellikle Task veya Task<T> döndürür.
- await Anahtar Kelimesi: Bir asenkron işlemin (örneğin, Task) tamamlanmasını beklemek için kullanılır. await, ana iş parçacığını bloke etmez; bunun yerine, asenkron işlem tamamlandığında metodun kalan kısmının çalıştırılmasını sağlar.
public async Task<string> DownloadWebPageAsync(string url)
{
using (HttpClient client = new HttpClient())
{
Console.WriteLine("Downloading..."); // Bu satır hemen çalışır
string result = await client.GetStringAsync(url); // Asenkron işlem (ağ isteği)
Console.WriteLine("Download complete."); // Bu satır, indirme işlemi bittikten sonra çalışır
return result;
}
}
// Kullanımı
public async Task ProcessWebPage() // Bu metod da async olmalı
{
string pageContent = await DownloadWebPageAsync("https://www.example.com");
Console.WriteLine($"Page length: {pageContent.Length}");
}
Açıklama:
- DownloadWebPageAsync metodu async ile işaretlenmiştir ve Task<string> döndürür.
- client.GetStringAsync(url) metodu, bir web sayfasını asenkron olarak indiren bir işlemdir (bir Task<string> döndürür).
- await anahtar kelimesi, GetStringAsync işleminin tamamlanmasını bekler. Bu sırada, ana iş parçacığı bloke edilmez; diğer işlemler (örneğin, kullanıcı arayüzü güncellemeleri) yapılabilir.
- İndirme işlemi tamamlandığında, result değişkenine indirilen sayfanın içeriği atanır ve metodun kalan kısmı çalıştırılır.
- ProcessWebPage metodu da async olarak işaretlenmeli ve DownloadWebPageAsync metodunu çağırırken await kullanmalıdır.
Soru 5: React’te State ve Props Arasındaki Fark Nedir? (C# Geliştiricisi için Genel Bir UI Sorusu)
Açıklama: React (veya benzeri bir UI framework’ü) kullanırken “state” ve “props” arasındaki temel farklar nelerdir? C# geliştiricisi olarak, bu kavramları C#’taki hangi yapılarla ilişkilendirirsiniz?
Cevap:
Props (Properties):
- Bir component’e dışarıdan (üst component’ten) veri aktarmak için kullanılır.
- Salt okunurdur (read-only). Bir component, kendi aldığı props’ları doğrudan değiştiremez.
- C#’taki bir metoda parametre geçirmeye benzetilebilir. Parametreler, metot içinde değiştirilemez (eğer ref veya out ile geçirilmediyse).
State:
- Bir component’in kendi içindeki verileri tutmak için kullanılır.
- Değişken (mutable). Bir component, kendi state’ini güncelleyebilir. State güncellendiğinde, component yeniden render edilir (yeniden çizilir).
- C#’taki bir sınıfın özel (private) alanlarına (fields) benzetilebilir. Sınıf, kendi alanlarını değiştirebilir.
C# ile İlişkilendirme:
| React Kavramı | C# Karşılığı (Benzetme) | Açıklama
| Props | Metot parametreleri | Bir metoda geçirilen parametreler gibi, props’lar da bir component’e dışarıdan veri aktarır. Props’lar, component içinde değiştirilemez (salt okunurdur).
| State | Sınıfın özel (private) alanları | Bir sınıfın kendi iç durumunu temsil eden özel alanlar gibi, state de bir component’in kendi iç verilerini tutar. State, component içinde değiştirilebilir ve bu değişiklikler component’in yeniden render edilmesine neden olur.
| Component | Method ya da Class | Bir method ya da Class, nasıl belli işlevleri yerine getirmek için yazılıyorsa, component’ler de kullanıcı arayüzünün (UI) belirli bir parçasını oluşturmak ve yönetmek için kullanılır. Büyük bir uygulamayı daha küçük, yönetilebilir parçalara ayırmak için kullanılır.
Örnek (Basitleştirilmiş):
// Üst Component (C# karşılığı: Ana Form veya Kontrol)
function ParentComponent() {
const [name, setName] = React.useState("John"); // State kullanımı (C#: private string name = "John";)
return (
<div>
<ChildComponent name={name} /> {/* Props ile veri aktarımı (C#: ChildMethod(name);) */}
<button onClick={() => setName("Jane")}>Change Name</button> {/* State'i güncelleme */}
</div>
);
}
// Alt Component (C# karşılığı: Alt Form veya Kontrol, veya bir Metot)
function ChildComponent(props) {
return (
<p>Hello, {props.name}!</p> // Props'u kullanma (salt okunur)
);
}
- ParentComponent, name adında bir state’e sahiptir.
- ChildComponent, name verisini props aracılığıyla alır.
- ChildComponent, aldığı name prop’unu değiştiremez.
- ParentComponent, setName fonksiyonunu kullanarak kendi name state’ini güncelleyebilir. State güncellendiğinde, ParentComponent ve dolayısıyla ChildComponent yeniden render edilir.
5. Veritabanı ve SQL Soruları
Bu bölüm, veritabanı yönetimi ve SQL sorgulama becerilerinizi ölçen sorulara odaklanır.
- Soru 1: SQL Sorguları
- Açıklama: Aşağıdaki Employees tablosundan en yüksek maaşı alan ikinci çalışanı bulan SQL sorgusunu yazın.
Employees (Table) employee_id (INT, PRIMARY KEY) first_name (VARCHAR) last_name (VARCHAR) salary (DECIMAL) department_id (INT)
SQL Çözümü (Çeşitli Yaklaşımlar):
Yöntem 1: DENSE_RANK() (En iyi yöntem, genellikle):
WITH RankedEmployees AS (
SELECT
employee_id,
first_name,
last_name,
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) as salary_rank
FROM
Employees
)
SELECT
employee_id,
first_name,
last_name,
salary
FROM
RankedEmployees
WHERE
salary_rank = 2;
Açıklama:
- WITH RankedEmployees AS (…): Bu, bir Common Table Expression (CTE) tanımlar. CTE, geçici bir sonuç kümesi oluşturur ve sorgunun geri kalanında kullanılabilir.
- DENSE_RANK() OVER (ORDER BY salary DESC): Bu, bir window function kullanır. DENSE_RANK(), her bir satıra, maaşına (salary) göre azalan sırada (DESC) bir sıra numarası (salary_rank) atar. DENSE_RANK(), aynı maaşa sahip çalışanlara aynı sıra numarasını verir ve sonraki sıra numarasını atlamaz (örneğin, 1, 2, 2, 3, …).
- SELECT … FROM RankedEmployees WHERE salary_rank = 2;: CTE’den (RankedEmployees), salary_rank değeri 2 olan satırları (yani, en yüksek ikinci maaşa sahip çalışanları) seçer.
Yöntem 2: OFFSET ve FETCH (SQL Server 2012 ve sonrası):
SELECT
employee_id,
first_name,
last_name,
salary
FROM
Employees
ORDER BY
salary DESC
OFFSET 1 ROWS
FETCH NEXT 1 ROWS ONLY;
- Açıklama:
- ORDER BY salary DESC: Sonuçları maaşa göre azalan sırada sıralar.
- OFFSET 1 ROWS: İlk 1 satırı atlar (en yüksek maaşlı çalışan).
- FETCH NEXT 1 ROWS ONLY: Sonraki 1 satırı alır (en yüksek ikinci maaşlı çalışan).
Yöntem 3: Alt Sorgu (Subquery) (Daha az verimli, eski yöntem):
SELECT
employee_id,
first_name,
last_name,
salary
FROM
Employees
WHERE
salary = (
SELECT MAX(salary)
FROM Employees
WHERE salary < (SELECT MAX(salary) FROM Employees)
);
Açıklama:
- En içteki (SELECT MAX(salary) FROM Employees) sorgusu, en yüksek maaşı bulur.
- Ortadaki (SELECT MAX(salary) FROM Employees WHERE salary < …) sorgusu, en yüksek maaştan daha düşük olan en yüksek maaşı bulur.
- En dıştaki sorgu, bu ikinci en yüksek maaşa sahip çalışanları seçer. Bu yöntem, genellikle daha az verimlidir çünkü birden fazla kez MAX fonksiyonunu kullanır ve veritabanı motorunun tabloyu birden fazla kez taramasına neden olabilir.
Soru 2: Indexleme
Açıklama: Veritabanında indexleme (indexing) nedir? Ne zaman kullanılır? Performansa etkisi nedir? C#’taki Entity Framework Core ile index nasıl oluşturulur?
Cevap:
Indexleme Nedir?
- Veritabanı index’i, bir tablodaki belirli sütunlardaki verilere hızlı erişim sağlamak için kullanılan özel bir veri yapısıdır (genellikle B-tree veya hash tablosu). Bir kitabın arkasındaki dizine (index) benzetilebilir. Kitabın tamamını taramak yerine, dizine bakarak aradığınız kelimenin hangi sayfalarda geçtiğini hızlıca bulabilirsiniz.
Ne Zaman Kullanılır?
- Sık Kullanılan Sorgular: Bir tablodaki belirli sütunlar, WHERE, JOIN, ORDER BY gibi sorgu koşullarında sık sık kullanılıyorsa, bu sütunlara index eklemek sorgu performansını önemli ölçüde artırabilir.
- Büyük Tablolar: Büyük tablolarda arama işlemleri yavaş olabilir. Index’ler, bu yavaşlığı gidermek için çok önemlidir.
- Benzersizlik (Uniqueness): Bir sütunun benzersiz değerler içermesi gerekiyorsa (örneğin, PRIMARY KEY, UNIQUE constraint), bu sütuna otomatik olarak bir index eklenir.
Performansa Etkisi:
Artıları:
- SELECT sorgularının hızını önemli ölçüde artırır (özellikle WHERE ve JOIN içeren sorgularda).
- ORDER BY ve GROUP BY işlemlerini hızlandırır.
Eksileri:
- INSERT, UPDATE ve DELETE işlemlerini yavaşlatabilir (çünkü index’lerin de güncellenmesi gerekir).
- Disk alanı kaplar (index’ler veritabanında ayrı bir yapı olarak saklanır).
Entity Framework Core ile Index Oluşturma:
Data Annotations:
public class Employee
{
public int EmployeeId { get; set; }
[Index(nameof(LastName), nameof(FirstName))] // Bileşik index (composite index)
public string FirstName { get; set; }
public string LastName { get; set; }
public decimal Salary { get; set; }
[Index(nameof(DepartmentId), IsUnique = false)] // Tek bir sutunda index
public int DepartmentId {get; set; }
}
Fluent API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasIndex(e => e.LastName); // Tek sütunlu index
modelBuilder.Entity<Employee>()
.HasIndex(e => new { e.LastName, e.FirstName }); // Bileşik index (composite index)
modelBuilder.Entity<Employee>()
.HasIndex(b => b.DepartmentId)
.IsUnique(false);
}
Soru 3: JOIN Türleri
Açıklama: INNER JOIN, LEFT JOIN, RIGHT JOIN ve FULL JOIN arasındaki farklar nelerdir? Örnek bir senaryo ile açıklayın.
Cevap:
- INNER JOIN: Yalnızca her iki tabloda da eşleşen satırları döndürür.
- LEFT JOIN (LEFT OUTER JOIN): Sol tablodaki tüm satırları ve sağ tablodaki eşleşen satırları döndürür. Sağ tabloda eşleşen satır yoksa, sağ tablonun sütunları için NULL değerler döndürülür.
- RIGHT JOIN (RIGHT OUTER JOIN): Sağ tablodaki tüm satırları ve sol tablodaki eşleşen satırları döndürür. Sol tabloda eşleşen satır yoksa, sol tablonun sütunları için NULL değerler döndürülür.
- FULL JOIN (FULL OUTER JOIN): Her iki tablodaki tüm satırları döndürür. Eşleşme olmayan satırlar için diğer tablonun sütunları NULL değerler içerir.
- Örnek Senaryo:
// Employees Tablosu employee_id | first_name | last_name | department_id -----------|------------|-----------|--------------- 1 | John | Doe | 10 2 | Jane | Smith | 20 3 | Peter | Jones | 10 4 | Mary | Brown | NULL // Departments Tablosu department_id | department_name -------------|---------------- 10 | Sales 20 | Marketing 30 | Engineering
INNER JOIN:
SELECT
e.first_name,
e.last_name,
d.department_name
FROM
Employees e
INNER JOIN
Departments d ON e.department_id = d.department_id;
-- Sonuç:
-- first_name | last_name | department_name
-- -----------|-----------|----------------
-- John | Doe | Sales
-- Jane | Smith | Marketing
-- Peter | Jones | Sales
LEFT JOIN:
SELECT
e.first_name,
e.last_name,
d.department_name
FROM
Employees e
LEFT JOIN
Departments d ON e.department_id = d.department_id;
-- Sonuç:
-- first_name | last_name | department_name
-- -----------|-----------|----------------
-- John | Doe | Sales
-- Jane | Smith | Marketing
-- Peter | Jones | Sales
-- Mary | Brown | NULL -- Mary'nin departmanı yok
RIGHT JOIN:
SELECT
e.first_name,
e.last_name,
d.department_name
FROM
Employees e
RIGHT JOIN
Departments d ON e.department_id = d.department_id;
-- Sonuç:
-- first_name | last_name | department_name
-- -----------|-----------|----------------
-- John | Doe | Sales
-- Peter | Jones | Sales
-- Jane | Smith | Marketing
-- NULL | NULL | Engineering -- Hiç çalışanı olmayan departman
FULL JOIN:
SELECT
e.first_name,
e.last_name,
d.department_name
FROM
Employees e
FULL JOIN
Departments d ON e.department_id = d.department_id;
-- Sonuç:
-- first_name | last_name | department_name
-- -----------|-----------|----------------
-- John | Doe | Sales
-- Peter | Jones | Sales
-- Jane | Smith | Marketing
-- Mary | Brown | NULL
-- NULL | NULL | Engineering
Soru 4: Transaction
Açıklama: Veritabanında transaction (işlem) nedir? ACID özellikleri nelerdir? C#’ta Entity Framework Core ile bir transaction nasıl yönetilir?
Cevap:
Transaction (İşlem): Bir veritabanında bir veya daha fazla işlemin (örneğin, INSERT, UPDATE, DELETE) tek bir birim olarak ele alınmasıdır. Ya tüm işlemler başarılı olur (commit edilir) ya da herhangi birinde hata olursa tüm işlemler geri alınır (rollback edilir). Bu, veri tutarlılığını sağlar.
ACID Özellikleri:
- Atomicity (Atomiklik): Bir transaction, bölünemez bir birimdir. Ya tüm işlemler başarılı olur ya da hiçbiri olmaz.
- Consistency (Tutarlılık): Bir transaction, veritabanını bir tutarlı durumdan başka bir tutarlı duruma getirir. Veritabanı kısıtlamaları (constraints) ihlal edilmez.
- Isolation (İzolasyon): Aynı anda çalışan transaction’lar birbirini etkilemez. Her transaction, sanki diğer transaction’lar yokmuş gibi çalışır.
- Durability (Kalıcılık): Bir transaction commit edildikten sonra, sistemde bir arıza olsa bile (örneğin, elektrik kesintisi) değişiklikler kalıcı olur.
Entity Framework Core ile Transaction Yönetimi:
- Otomatik Transaction (Varsayılan): SaveChanges() veya SaveChangesAsync() metodu çağrıldığında, Entity Framework Core otomatik olarak bir transaction başlatır. Eğer tüm işlemler başarılı olursa, transaction commit edilir. Herhangi bir hata olursa, transaction rollback edilir.
- Manuel Transaction: Birden çok SaveChanges() çağrısını tek bir transaction içinde birleştirmek veya daha ince ayarlı kontrol gerektiğinde manuel transaction kullanılabilir.
using (var context = new MyDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
// İşlemler...
context.Employees.Add(new Employee { FirstName = "Alice", LastName = "Smith", Salary = 50000 });
context.SaveChanges();
context.Departments.Add(new Department { DepartmentName = "HR" });
context.SaveChanges();
transaction.Commit(); // Tüm işlemleri onayla
}
catch (Exception)
{
transaction.Rollback(); // Hata durumunda tüm işlemleri geri al
throw; // Hatayı yeniden fırlat (isteğe bağlı)
}
}
}
Açıklama:
- using (var context = new MyDbContext()): DbContext nesnesini oluşturur ve using bloğu sayesinde, blok bittiğinde otomatik olarak dispose edilir (kaynaklar serbest bırakılır).
- using (var transaction = context.Database.BeginTransaction()): BeginTransaction() metodu ile yeni bir transaction başlatılır. Yine using bloğu kullanılır, böylece transaction da otomatik olarak dispose edilir.
- try…catch: İşlemler try
using (var context = new MyDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
// İşlemler...
context.Employees.Add(new Employee { FirstName = "Alice", LastName = "Smith", Salary = 50000 });
context.SaveChanges();
context.Departments.Add(new Department { DepartmentName = "HR" });
context.SaveChanges();
transaction.Commit(); // Tüm işlemleri onayla
}
catch (Exception)
{
transaction.Rollback(); // Hata durumunda tüm işlemleri geri al
throw; // Hatayı yeniden fırlat (isteğe bağlı)
}
}
}
3. try…catch: İşlemler try bloğu içinde yapılır. Herhangi bir hata oluşursa, catch bloğuna geçilir.
4. context.Employees.Add(…) ve context.Departments.Add(…): Yeni nesneler eklenir (ancak henüz veritabanına kaydedilmez).
5. context.SaveChanges(): Değişiklikler veritabanına kaydedilmeye çalışılır. Bu noktada, Entity Framework Core otomatik olarak bir transaction başlatır (eğer manuel bir transaction başlatılmamışsa).
6. transaction.Commit(): Eğer tüm işlemler başarılı olursa, transaction.Commit() çağrılarak transaction onaylanır (commit edilir) ve değişiklikler kalıcı hale getirilir.
7. transaction.Rollback(): Eğer try bloğu içinde herhangi bir hata oluşursa, catch bloğuna geçilir ve transaction.Rollback() çağrılarak transaction geri alınır (rollback edilir). Bu, yapılan tüm değişikliklerin (eklemeler, güncellemeler, silmeler) iptal edilmesini sağlar.
8. throw;: catch bloğu içinde throw; kullanmak, hatayı tekrar fırlatır. Bu, hatanın daha üst katmanlar (örneğin, kullanıcı arayüzü) tarafından yakalanmasını ve işlenmesini sağlar. Bu kısım isteğe bağlıdır. Eğer hata burada işlenip, daha yukarıya fırlatılması istenmiyorsa throw; satırı kaldırılabilir.
Davranışsal Sorular
Bu bölüm, geçmiş deneyimlerinizi, takım çalışması becerilerinizi, problem çözme yaklaşımınızı ve iletişim yeteneklerinizi değerlendiren sorulara odaklanır. Bu sorulara net, özlü ve dürüst cevaplar vermek önemlidir.
Soru 1: Zorlu Bir Proje
Açıklama: Şimdiye kadar üzerinde çalıştığınız en zorlu proje hangisiydi? Karşılaştığınız zorluklar nelerdi? Bu zorlukların üstesinden nasıl geldiniz?
Cevap Örneği (STAR Metodu Kullanılarak):
- Situation (Durum): Önceki şirketimde, büyük bir e-ticaret platformu için yeni bir ödeme sistemi entegrasyonu projesinde görev aldım. Proje, mevcut sisteme minimum kesinti ile yeni bir ödeme sağlayıcısını entegre etmeyi hedefliyordu.
- Task (Görev): Benim görevim, ödeme sağlayıcısının API’si ile bizim sistemimiz arasında güvenli ve ölçeklenebilir bir iletişim katmanı oluşturmaktı. Aynı zamanda, eski ödeme sistemi ile uyumluluğu korumak ve geçiş sürecini yönetmek de gerekiyordu.
Action (Eylem):
- Ödeme sağlayıcısının API dokümantasyonunu detaylı bir şekilde inceledim.
- C# ve ASP.NET Core kullanarak, ödeme sağlayıcısının API’si ile iletişim kuran bir servis katmanı geliştirdim. Bu katman, kimlik doğrulama, yetkilendirme, istek/yanıt işleme ve hata yönetimi gibi işlevleri içeriyordu.
- Olası hataları ve güvenlik açıklarını tespit etmek için kapsamlı birim testleri (unit tests) ve entegrasyon testleri yazdım.
- Performans testleri yaparak sistemin ölçeklenebilirliğini ve yük altında nasıl davrandığını değerlendirdim.
- Eski ödeme sistemi ile uyumluluğu sağlamak için, yeni ödeme sistemine kademeli bir geçiş stratejisi geliştirdim ve uyguladım. Bu, kullanıcıların bir kısmının yeni sistemi kullanırken, diğerlerinin eski sistemi kullanmaya devam etmesini sağladı.
- Ekip üyeleriyle düzenli olarak toplantılar yaparak ilerlemeyi takip ettim ve olası sorunları erken aşamada tespit etmeye çalıştım.
Result (Sonuç): Proje, planlanan zamanda ve bütçe dahilinde başarıyla tamamlandı. Yeni ödeme sistemi, daha güvenli, daha hızlı ve daha ölçeklenebilir bir ödeme deneyimi sundu. Müşteri memnuniyeti arttı ve ödeme işlemlerindeki hata oranı azaldı. Kademeli geçiş stratejisi sayesinde, geçiş süreci sorunsuz bir şekilde yönetildi.
Soru 2: Takım Çalışması
Açıklama: Takım halinde çalışırken yaşadığınız bir anlaşmazlığı (çatışmayı) ve bu anlaşmazlığı nasıl çözdüğünüzü anlatın.
Cevap Örneği (STAR Metodu):
- Situation (Durum): Bir mobil uygulama geliştirme projesinde, bir özellik için kullanılacak en iyi yaklaşım konusunda ekip arkadaşımla aramızda bir anlaşmazlık çıktı. Ben, performansın daha iyi olacağını düşündüğüm A yöntemini savunuyordum, ekip arkadaşım ise B yönteminin daha kolay uygulanabilir ve daha az riskli olduğunu düşünüyordu.
- Task (Görev): Amacımız, hem kullanıcı deneyimini olumsuz etkilemeyecek hem de projenin zaman çizelgesine uygun bir çözüm bulmaktı.
Action (Eylem):
- Öncelikle, her iki yaklaşımın da artılarını ve eksilerini objektif bir şekilde değerlendirmek için bir liste hazırladım.
- Ekip arkadaşımla birlikte bu listeyi gözden geçirdik ve her bir noktanın önem derecesini tartıştık.
- Performansın kritik bir faktör olduğunu kabul ettik, ancak B yönteminin de uygulanabilirlik açısından önemli avantajları olduğunu gördük.
- A yönteminin performans avantajlarını B yönteminin basitliğiyle birleştiren hibrit bir çözüm geliştirmeye karar verdik. Bu çözüm, A yönteminin temel performans iyileştirmelerini içeriyor, ancak B yönteminin kolay uygulanabilirliğini koruyordu.
- Bu hibrit çözümü prototipleyerek test ettik ve her iki tarafın da beklentilerini karşıladığını gördük.
Result (Sonuç): Anlaşmazlığı yapıcı bir şekilde çözerek, hem performanslı hem de uygulanabilir bir çözüm bulduk. Bu olay, ekip içinde açık iletişimin ve farklı bakış açılarının değerini bir kez daha anlamamı sağladı. Ayrıca, ekip arkadaşımla daha güçlü bir çalışma ilişkisi kurmama yardımcı oldu.
Soru 3: Hata Yönetimi
Açıklama: Üretim ortamında (production) kritik bir hata ile karşılaştığınızda nasıl bir yaklaşım izlersiniz?
Cevap Örneği (STAR Metodu):
- Situation (Durum): Önceki çalıştığım şirkette, kullanıcıların profillerini güncelledikleri bir web uygulamasında, nadiren ortaya çıkan ancak kritik bir hata tespit edildi. Bazen profil güncellemeleri veritabanına doğru şekilde kaydedilmiyor ve kullanıcı verileri kayboluyordu.
- Task (Görev): Benim görevim, hatanın kaynağını hızlı bir şekilde bulmak, düzeltmek ve veri kaybını en aza indirmekti.
- Action (Eylem):Hata Tespiti ve İzleme: İlk olarak, hata raporlarını ve log kayıtlarını inceleyerek hatanın ne zaman ve hangi koşullarda ortaya çıktığını belirlemeye çalıştım. Sistemdeki izleme araçlarını (monitoring tools) kullanarak hatanın sıklığını ve etkilediği kullanıcı sayısını takip ettim.
- Acil Düzeltme (Hotfix): Veri kaybını önlemek için, profil güncelleme özelliğini geçici olarak devre dışı bıraktım. Bu, hatanın daha fazla kullanıcıyı etkilemesini engelledi. Kullanıcılara, bakım çalışması yapıldığına dair bir bilgilendirme mesajı gösterdim.
- Hata Ayıklama (Debugging): Kodu adım adım inceleyerek (debug ederek) hatanın kaynağını bulmaya çalıştım. Veritabanı sorgularını, transaction yönetimini ve veri doğrulama (validation) işlemlerini kontrol ettim.
- Hata Tekrar Oluşturma: Hatanın nedenini daha iyi anlamak için, hatayı yerel geliştirme ortamımda (local development environment) tekrar oluşturmaya çalıştım. Farklı senaryoları deneyerek hatanın hangi durumlarda tetiklendiğini belirledim.
- Çözüm: Hatanın, veritabanı transaction’larının doğru şekilde yönetilmemesinden kaynaklandığını tespit ettim. Bazı durumlarda, transaction commit edilmeden önce bir hata oluşuyor ve değişiklikler geri alınıyordu, ancak kullanıcıya güncellemenin başarılı olduğuna dair bir mesaj gösteriliyordu. Transaction yönetimini düzelten ve hata durumunda kullanıcıya doğru bir geri bildirim veren bir kod değişikliği yaptım.
- Test: Yaptığım değişikliği kapsamlı bir şekilde test ettim. Hem birim testleri (unit tests) hem de entegrasyon testleri (integration tests) yazdım. Farklı senaryoları simüle ederek hatanın tekrar ortaya çıkmadığından emin oldum.
- Dağıtım (Deployment): Testlerden başarıyla geçen düzeltmeyi, önce bir test ortamına (staging environment), ardından da üretim ortamına (production environment) dikkatli bir şekilde dağıttım. Dağıtım sırasında sistemi yakından takip ettim.
- İzleme ve Geri Bildirim: Düzeltmenin yayınlanmasından sonra, sistemi ve hata raporlarını yakından izlemeye devam ettim. Kullanıcılardan geri bildirim topladım ve herhangi bir sorun olup olmadığını kontrol ettim.
- Kök Neden Analizi (Root Cause Analysis): Olaydan sonra, ekip arkadaşlarımla birlikte bir kök neden analizi yaparak hatanın neden ortaya çıktığını ve gelecekte benzer hataları nasıl önleyebileceğimizi tartıştık. Kod inceleme (code review) süreçlerini iyileştirmeye ve daha fazla otomatik test yazmaya karar verdik.
Result (Sonuç): Hata, hızlı bir şekilde tespit edildi ve düzeltildi. Veri kaybı en aza indirildi ve kullanıcılar bilgilendirildi. Kök neden analizi sayesinde, gelecekte benzer hataların oluşma olasılığı azaltıldı.
Soru 4: Zaman Yönetimi
Açıklama: Sıkı bir teslim tarihi (deadline) olan bir projede, birden fazla görevi aynı anda yönetmeniz gerektiğinde nasıl bir yaklaşım izlersiniz?
Cevap Örneği:
- Görevleri Listeleme ve Önceliklendirme: Öncelikle, yapmam gereken tüm görevleri listelerim. Her görevin önem derecesini ve aciliyetini belirlerim. Bu amaçla, Eisenhower Matrisi (Acil/Önemli Matrisi) gibi önceliklendirme tekniklerini kullanabilirim.
- Zaman Çizelgesi Oluşturma: Her görev için ne kadar zaman ayıracağımı tahmin ederim ve bir zaman çizelgesi oluştururum. Bu çizelgede, görevlerin başlama ve bitiş tarihlerini, ara hedefleri (milestones) ve olası gecikmeleri göz önünde bulundururum.
- Parçalara Ayırma: Büyük ve karmaşık görevleri daha küçük, yönetilebilir alt görevlere bölerim. Bu, iş yükünü daha az göz korkutucu hale getirir ve ilerlemeyi takip etmeyi kolaylaştırır.
- Odaklanma ve Dikkat Dağıtıcıları Engelleme: Belirli bir görev üzerinde çalışırken, dikkatimi dağıtabilecek unsurları (örneğin, e-postalar, sosyal medya bildirimleri) en aza indirmeye çalışırım. Pomodoro Tekniği gibi zaman yönetimi tekniklerini kullanarak, belirli aralıklarla çalışır ve kısa molalar veririm.
- İletişim ve İşbirliği: Proje yöneticisi ve ekip arkadaşlarımla düzenli olarak iletişim kurarım. İlerleme durumu hakkında bilgi verir, olası sorunları erkenden paylaşır ve yardım istemekten çekinmem.
- Esneklik: Planımda beklenmedik değişiklikler veya gecikmeler olabileceğini kabul ederim. Bu tür durumlarda, öncelikleri yeniden değerlendirir ve zaman çizelgesini güncellerim. Gerekirse, bazı görevleri erteleyebilir veya delege edebilirim.
- Araçlar: Zaman yönetimi ve görev takibi için çeşitli araçlar (örneğin, Trello, Jira, Asana, Microsoft To Do) kullanırım. Bu araçlar, görevleri görselleştirmeme, ilerlemeyi takip etmeme ve ekip arkadaşlarımla işbirliği yapmama yardımcı olur.
- Öğrenme ve Gelişim: Her projeden sonra, zaman yönetimi becerilerimi nasıl geliştirebileceğimi değerlendiririm. Hangi yöntemlerin işe yaradığını, hangilerinin yaramadığını belirler ve bir sonraki projede daha iyi bir zaman yönetimi stratejisi uygulamaya çalışırım.
7. Kod İnceleme ve Debug Soruları
Bu bölüm, kod okuma, hata ayıklama ve kod kalitesini değerlendirme becerilerinizi ölçen sorulara odaklanır.
Soru 1: Kod İnceleme
Açıklama: Aşağıdaki C# kod parçasını inceleyin. Hataları, olası sorunları ve iyileştirme önerilerinizi belirtin.
public class Calculator
{
public int Calculate(string operation, int a, int b)
{
if (operation == "add")
{
return a + b;
}
else if (operation == "subtract")
{
return a - b;
}
else if (operation == "multiply")
{
return a * b;
}
else if (operation == "divide")
{
if (b == 0)
{
return -1; // Hata kodu
}
return a / b;
}
return 0; // Geçersiz işlem
}
}
Cevap:
Hatalar ve Olası Sorunlar:
- Hata Yönetimi: Sıfıra bölme durumunda -1 döndürmek iyi bir hata yönetimi yaklaşımı değildir. Bunun yerine, bir DivideByZeroException fırlatılmalıdır. Geçersiz bir işlem durumunda 0 döndürmek de yanıltıcı olabilir; bunun yerine bir ArgumentException fırlatılmalıdır.
- String Karşılaştırması: operation string’ini karşılaştırmak için == operatörü yerine string.Equals(operation, “add”, StringComparison.OrdinalIgnoreCase) kullanılmalıdır. Bu, büyük/küçük harf duyarlılığını ortadan kaldırır ve daha güvenli bir karşılaştırma sağlar.
- Esneklik ve Genişletilebilirlik: Kod, yeni işlemler eklemeye uygun değildir. Her yeni işlem için if/else if bloğuna bir koşul eklemek gerekir. Bu, kodun bakımını zorlaştırır.
- Magic Number: -1 ve 0 gibi “sihirli sayılar” kullanmak, kodun okunabilirliğini azaltır. Bunlar yerine anlamlı sabitler (constants) tanımlanmalıdır.
- Sınıfın Durumu Yok: Bu senaryo için Calculator class’ının bir instance’ı olamasına gerek yok static bir method ile çözülebilir.
İyileştirme Önerileri:
public static class Calculator //static class yaptık
{
// Sabitler (Constants)
private const int ErrorResult = -1; // Anlamlı bir isim
public static int Calculate(string operation, int a, int b)
{
// String karşılaştırması (case-insensitive)
if (string.Equals(operation, "add", StringComparison.OrdinalIgnoreCase))
{
return a + b;
}
else if (string.Equals(operation, "subtract", StringComparison.OrdinalIgnoreCase))
{
return a - b;
}
else if (string.Equals(operation, "multiply", StringComparison.OrdinalIgnoreCase))
{
return a * b;
}
else if (string.Equals(operation, "divide", StringComparison.OrdinalIgnoreCase))
{
if (b == 0)
{
throw new DivideByZeroException("Cannot divide by zero."); // Hata fırlatma
}
return a / b;
}
else
{
throw new ArgumentException($"Invalid operation: {operation}", nameof(operation)); // Hata fırlatma
}
}
// Dictionary kullanarak işlem ve fonksiyon eşleştirmesi
private static readonly Dictionary<string, Func<int, int, int>> operations =
new Dictionary<string, Func<int, int, int>>(StringComparer.OrdinalIgnoreCase) // Case-insensitive key
{
{ "add", (x, y) => x + y },
{ "subtract", (x, y) => x - y },
{ "multiply", (x, y) => x * y },
{ "divide", (x, y) =>
{
if (y == 0)
{
throw new DivideByZeroException("Cannot divide by zero.");
}
return x / y;
}
}
};
public static int CalculateWithDictionary(string operation, int a, int b)
{
if (operations.TryGetValue(operation, out Func<int, int, int> func))
{
return func(a, b);
}
else
{
throw new ArgumentException($"Invalid operation: {operation}", nameof(operation));
}
}
}
Açıklama (İyileştirilmiş Kod):
- Hata Yönetimi: DivideByZeroException ve ArgumentException fırlatılarak daha iyi bir hata yönetimi sağlandı.
- String Karşılaştırması: string.Equals ve StringComparison.OrdinalIgnoreCase kullanılarak büyük/küçük harf duyarsız ve daha güvenli bir karşılaştırma yapıldı.
- Ölçeklenebilirlik: operations adında bir Dictionary kullanılarak, işlem adları (string) ile bu işlemleri gerçekleştiren fonksiyonlar (lambda ifadeleri) eşleştirildi. Yeni bir işlem eklemek, sadece bu dictionary’e yeni bir girdi eklemek kadar kolaydır. CalculateWithDictionary metodu, bu dictionary’i kullanarak işlemi gerçekleştirir.
- Static Class: Bu method bir instance’a ihtiyaç duymuyor, bu nedenle static olarak tanımlanması daha uygun. TryGetValue: Dictionary’de bir anahtarın olup olmadığını kontrol etmenin güvenli yolu TryGetValue kullanmaktır. Eğer anahtar yoksa, KeyNotFoundException fırlatılmaz; bunun yerine false döndürülür ve out parametresi (func in bu durumda) null olur.
Soru 2: Debug
Açıklama: Aşağıdaki C# kodunda bir hata var. Hatayı bulun, nedenini açıklayın ve düzeltin.
public class Node
{
public int Data;
public Node Next;
}
public class LinkedList
{
public Node Head;
public void Add(int data)
{
Node newNode = new Node { Data = data };
if (Head == null)
{
Head = newNode;
return;
}
Node current = Head;
while (current != null) // Hata burada
{
current = current.Next;
}
current.Next = newNode; //Hata burada
}
public void Print()
{
Node current = Head;
while (current != null)
{
Console.WriteLine(current.Data);
current = current.Next;
}
}
}
Cevap:
- Hata: Add metodundaki while döngüsü, current değişkeni null olana kadar devam ediyor. Döngü bittiğinde, current değişkeni null değerindedir. Sonrasında current.Next = newNode; satırı, bir NullReferenceException fırlatır (çünkü null bir nesnenin Next özelliği olamaz).
- Neden: Döngü, listenin sonuna kadar gitmeli, ancak son elemanı geçmemeli. Yeni düğüm, son elemanın Next özelliğine bağlanmalı.
- Düzeltme: while döngüsünün koşulu current.Next != null olarak değiştirilmelidir.
public void Add(int data)
{
Node newNode = new Node { Data = data };
if (Head == null)
{
Head = newNode;
return;
}
Node current = Head;
while (current.Next != null) // Düzeltilmiş döngü koşulu
{
current = current.Next;
}
current.Next = newNode;
}
Açıklama (Düzeltilmiş Kod): while (current.Next != null) koşulu, current değişkeni son elemanı gösterene kadar döngünün devam etmesini sağlar. Döngü bittiğinde, current değişkeni son elemanı gösterir ve current.Next = newNode; satırı, yeni düğümü son elemanın Next özelliğine bağlar.
Soru 3: Bir program neden yavaş çalışıyor olabilir?
Açıklama: Bir C# uygulamasının yavaş çalışmasının olası nedenleri nelerdir? Bu yavaşlığı gidermek için neler yapılabilir?
Cevap:
Olası Nedenler:
- Verimsiz Algoritmalar: Yanlış veya verimsiz algoritmalar kullanmak (örneğin, O(n^2) yerine O(n log n) karmaşıklığına sahip bir algoritma kullanmak).
- Veri Yapısı Seçimi: Yanlış veri yapısı kullanmak (örneğin, sık sık arama yapılan bir durumda List<T> yerine HashSet<T> kullanmamak).
- Veritabanı Sorguları: Yavaş çalışan SQL sorguları. İndekslerin eksik veya yanlış olması. Çok fazla veri çekmek (gereksiz sütunları veya satırları çekmek). N+1 problemi (Entity Framework gibi ORM’lerde sık karşılaşılan bir sorun).
- Ağ Gecikmeleri: Uzak sunuculara yapılan isteklerin yavaş olması (örneğin, ağ bağlantısının yavaş olması, sunucunun yanıt vermesinin uzun sürmesi).
- I/O İşlemleri: Dosya okuma/yazma işlemlerinin yavaş olması (örneğin, büyük dosyalarla çalışmak, disk I/O’sunun yavaş olması).
- Bellek Yönetimi: Çok fazla bellek kullanmak (bellek sızıntıları, gereksiz büyük nesneler oluşturmak). Sık sık çöp toplama (garbage collection) işleminin tetiklenmesi.
- Senkron İşlemler: Uzun süren işlemleri senkron olarak yapmak (ana iş parçacığını bloke etmek).
- Kilitlenme (Deadlock): Birden fazla iş parçacığının (thread) birbirini beklemesi ve hiçbirinin ilerleyememesi.
- Yanlış Threading Kullanımı: Aşırı thread oluşturmak veya thread’ler arasında senkronizasyon sorunları yaşamak.
- Dış Kaynaklar: Üçüncü taraf kütüphanelerin veya servislerin yavaş çalışması.
- Kod Kalitesi: Gereksiz döngüler, karmaşık ve iç içe geçmiş koşullar, gereksiz nesne oluşturma gibi kod kalitesi sorunları.
- Donanım: Yetersiz CPU, RAM veya disk alanı.
Yavaşlığı Giderme Yöntemleri:
- Profiling (Performans Analizi): Bir profiler (örneğin, Visual Studio’daki Performance Profiler, dotTrace, ANTS Performance Profiler) kullanarak uygulamanın hangi bölümlerinin yavaş çalıştığını tespit etmek. Profiler, CPU kullanımı, bellek kullanımı, veritabanı sorguları, ağ istekleri ve diğer metrikler hakkında detaylı bilgi sağlar.
- Algoritma ve Veri Yapısı Optimizasyonu: Verimsiz algoritmaları ve veri yapılarını daha verimli olanlarla değiştirmek.
- Veritabanı Optimizasyonu: SQL sorgularını optimize etmek (örneğin, gereksiz sütunları çekmemek, JOIN işlemlerini optimize etmek, WHERE koşullarını doğru kullanmak). İndeksleri doğru kullanmak (eksik indeksleri eklemek, gereksiz indeksleri kaldırmak). Veritabanı bağlantılarını doğru yönetmek (connection pooling kullanmak). N+1 probleminden kaçınmak (Entity Framework’de Include veya projeksiyon kullanarak).
- Ağ Optimizasyonu: Ağ isteklerinin sayısını azaltmak, istekleri asenkron olarak yapmak, yanıtları sıkıştırmak (örneğin, GZIP), CDN (Content Delivery Network) kullanmak.
- I/O Optimizasyonu: Dosya okuma/yazma işlemlerini asenkron olarak yapmak, buffering kullanmak, gereksiz dosya işlemlerinden kaçınmak.
- Bellek Yönetimi: Bellek sızıntılarını tespit etmek ve gidermek (profiler kullanarak). Büyük nesneleri gereksiz yere bellekte tutmaktan kaçınmak. IDisposable arayüzünü uygulayan nesneleri using bloğu içinde kullanmak veya Dispose() metodunu çağırarak kaynakları serbest bırakmak.
- Asenkron Programlama: Uzun süren işlemleri async ve await kullanarak asenkron olarak yapmak. Bu, ana iş parçacığının bloke olmasını engeller ve uygulamanın tepkisel kalmasını sağlar.
- Threading: Kilitlenmeleri (deadlocks) önlemek için dikkatli bir şekilde thread kullanmak. Thread havuzu (thread pool) kullanmak (gereksiz thread oluşturmaktan kaçınmak). lock deyimi gibi senkronizasyon mekanizmalarını doğru kullanmak. Mümkünse, thread-safe veri yapıları (ConcurrentDictionary, ConcurrentQueue, vb.) kullanmak.
- Caching: Sık erişilen verileri önbelleğe almak (örneğin, Redis, Memcached, veya in-memory caching).
- Kod İncelemesi (Code Review): Kod kalitesini artırmak ve olası performans sorunlarını erken aşamada tespit etmek için düzenli olarak kod incelemesi yapmak.
- Yük Testi (Load Testing): Uygulamanın yük altında nasıl davrandığını test etmek için yük testleri yapmak. Bu, darboğazları (bottlenecks) tespit etmeye ve performansı iyileştirmeye yardımcı olur.
- Donanım Kaynaklarını Artırmak: Eğer sorun donanım yetersizliğinden kaynaklanıyorsa, CPU, RAM veya disk alanını artırmak gerekebilir.
Genel Kültür ve Güncel Teknolojiler
Bu bölüm, adayın teknoloji dünyasındaki güncel trendlere ne kadar hakim olduğunu ve genel teknoloji bilgisi seviyesini ölçmeyi amaçlar.
Soru 1: Microservices ve Monolith
Açıklama: Microservices (mikroservisler) ve monolith (monolitik) mimarileri arasındaki temel farklar nelerdir? Avantajları ve dezavantajları nelerdir? Hangi durumlarda hangi mimari tercih edilmelidir?
Cevap:
Monolith (Monolitik) Mimari:
- Tanım: Tüm uygulamanın tek bir birim (single unit) olarak geliştirildiği ve dağıtıldığı mimari tarzıdır. Genellikle tek bir veritabanı kullanılır.
- Avantajları: Basitlik: Geliştirilmesi, test edilmesi ve dağıtılması daha kolaydır (başlangıçta).
- Daha Az Operasyonel Yük: Yönetilecek daha az bileşen vardır.
- Daha Kolay Debugging: Hata ayıklama ve izleme genellikle daha kolaydır (tüm kod tek bir yerde olduğu için).
- Dezavantajları: Ölçeklenebilirlik Zorlukları: Uygulamanın sadece belirli bölümleri yoğun trafik alsa bile, tüm uygulamanın ölçeklenmesi gerekir. Bu, kaynak israfına neden olabilir. Esneklik Eksikliği: Yeni teknolojiler veya diller kullanmak zordur. Tüm uygulamayı etkilemeden değişiklik yapmak zor olabilir.
- Sıkı Bağlılık (Tight Coupling): Farklı modüller arasındaki sıkı bağlılık, bir modüldeki değişikliğin diğer modülleri etkilemesine neden olabilir.
- Uzun Dağıtım Süreleri: Küçük bir değişiklik bile tüm uygulamanın yeniden dağıtılmasını gerektirebilir.
- Teknoloji Bağımlılığı: Tüm uygulama için tek bir teknoloji yığını (stack) seçmek zorunluluğu vardır.
Microservices (Mikroservisler) Mimarisi:
Tanım: Uygulamanın, her biri kendi sorumluluğuna sahip, bağımsız olarak geliştirilebilen, dağıtılabilen ve ölçeklenebilen küçük servislere (mikroservisler) bölündüğü mimari tarzıdır. Her mikroservis, genellikle kendi veritabanını kullanır.
Avantajları:
- Ölçeklenebilirlik: Her servis bağımsız olarak ölçeklenebilir. Bu, kaynakların daha verimli kullanılmasını sağlar.
- Esneklik: Her servis farklı teknolojiler veya diller kullanılarak geliştirilebilir.
- Gevşek Bağlılık (Loose Coupling): Servisler arasındaki gevşek bağlılık, bir servisteki değişikliğin diğer servisleri etkileme olasılığını azaltır.
- Hızlı Dağıtım: Her servis bağımsız olarak dağıtılabilir. Bu, daha sık ve daha hızlı dağıtım yapılmasını sağlar.
- Teknoloji Çeşitliliği: Farklı servisler için farklı teknolojiler kullanılabilir.
- Hata İzolasyonu: Bir serviste meydana gelen hata, diğer servisleri etkilemez (doğru tasarlandığında).
- Dezavantajları: Karmaşıklık: Dağıtık bir sistem olduğu için, monolitik bir uygulamaya göre daha karmaşıktır.
- Daha Fazla Operasyonel Yük: Yönetilecek daha fazla bileşen vardır (servisler, veritabanları, ağ iletişimi vb.).
- İzleme ve Hata Ayıklama Zorluğu: Hata ayıklama ve izleme daha karmaşık olabilir (birden fazla servisi izlemek gerekir).
- Veri Tutarlılığı: Farklı servislerin kendi veritabanlarını kullanması, veri tutarlılığını sağlamayı zorlaştırabilir (eventual consistency, distributed transactions).
- Ağ İletişimi: Servisler arası iletişim ağ üzerinden yapıldığı için gecikme (latency) ve güvenilirlik sorunları olabilir.
Hangi Durumlarda Hangi Mimari Tercih Edilmeli?
Monolith:
- Küçük ve orta ölçekli uygulamalar.
- Hızlı prototipleme (MVP – Minimum Viable Product).
- Ekip boyutu küçükse ve deneyimi sınırlıysa.
- Karmaşıklığın düşük tutulması gerekiyorsa.
Microservices:
- Büyük ve karmaşık uygulamalar.
- Yüksek ölçeklenebilirlik ve esneklik gereksinimi varsa.
- Farklı ekiplerin bağımsız olarak çalışması gerekiyorsa.
- Sürekli dağıtım (continuous delivery) ve DevOps pratikleri uygulanıyorsa.
- Farklı teknolojilerin kullanılması gerekiyorsa.
Soru 2: REST vs GraphQL
Açıklama: REST API ve GraphQL arasındaki temel farklar nelerdir? Avantajları ve dezavantajları nelerdir? Hangi durumlarda hangi yaklaşım tercih edilmelidir?
Cevap:
REST (Representational State Transfer):
- Tanım: Bir web API’si tasarlamak için kullanılan bir mimari stildir. Kaynaklar (resources) URL’ler ile temsil edilir ve HTTP metotları (GET, POST, PUT, DELETE) kullanılarak bu kaynaklar üzerinde işlemler yapılır.
Avantajları:
- Basitlik: Anlaşılması ve uygulanması kolaydır.
- Yaygınlık: Çok yaygın olarak kullanılır ve birçok araç ve kütüphane mevcuttur.
- Önbellekleme (Caching): HTTP önbellekleme mekanizmaları kullanılabilir.
- Stateless (Durumsuz): Sunucu, istemci hakkında herhangi bir durum bilgisi tutmaz. Bu, ölçeklenebilirliği artırır.
Dezavantajları:
- Over-fetching (Gereğinden Fazla Veri Çekme): İstemci, ihtiyacı olandan daha fazla veri çekebilir. Örneğin, bir kullanıcı listesi çekerken, sadece kullanıcı adlarına ihtiyaç duyulsa bile, tüm kullanıcı bilgiler
- Under-fetching (Gereğinden Az Veri Çekme): İstemci, ihtiyacı olan tüm verileri tek bir istekte alamazsa, birden fazla istek yapması gerekebilir. Örneğin, bir kullanıcının bilgilerini ve o kullanıcının siparişlerini almak için iki ayrı istek yapılması gerekebilir. Bu, “N+1” problemine benzer bir durumdur.
- Esneklik Eksikliği: API’deki değişiklikler, istemcileri etkileyebilir. Örneğin, bir alanın adı değişirse, istemcilerin de güncellenmesi gerekir.
- Çok Sayıda Endpoint: Farklı veri gereksinimleri için çok sayıda endpoint (uç nokta) tanımlanması gerekebilir.
GraphQL:
Tanım: Facebook tarafından geliştirilen bir sorgu dili ve çalışma zamanıdır (runtime). İstemcinin, tam olarak hangi verilere ihtiyacı olduğunu belirtmesini sağlar. Tek bir endpoint üzerinden veri alışverişi yapılır.
Avantajları:
- Verimli Veri Çekme: İstemci, tam olarak ihtiyacı olan verileri çeker. Over-fetching ve under-fetching sorunları ortadan kalkar.
- Tek Endpoint: Tüm veri ihtiyaçları için tek bir endpoint kullanılır.
- Güçlü Tip Sistemi (Strong Typing): GraphQL, bir şema (schema) kullanır. Bu şema, veri tiplerini ve sorgu yapısını tanımlar. Bu, hataları erken aşamada yakalamaya ve daha iyi dokümantasyon oluşturmaya yardımcı olur.
- Introspection: GraphQL, kendi şemasını sorgulama yeteneğine sahiptir. Bu, geliştiricilerin API’yi keşfetmesini kolaylaştırır.
- Daha İyi Geliştirici Deneyimi: Geliştiriciler, ihtiyaç duydukları verileri tam olarak belirtebildikleri için daha verimli çalışabilirler.
Dezavantajları:
- Öğrenme Eğrisi: REST’e göre daha karmaşıktır ve öğrenmesi biraz daha zaman alabilir.
- Önbellekleme (Caching): HTTP önbellekleme mekanizmaları doğrudan kullanılamaz. GraphQL için özel önbellekleme çözümleri kullanmak gerekir.
- Karmaşık Sorgular: Kötü tasarlanmış veya aşırı karmaşık sorgular, sunucuyu yorabilir.
- Dosya Yükleme: Dosya yükleme gibi işlemler için özel çözümler gerektirir.
- Yaygınlık: REST kadar yaygın değildir, bu nedenle daha az araç ve kütüphane mevcuttur.
Hangi Durumlarda Hangi Yaklaşım Tercih Edilmeli?
REST:
- Basit API’ler.
- Kaynakların (resources) iyi tanımlandığı ve sık değişmediği durumlar.
- HTTP önbelleklemesinin önemli olduğu durumlar.
- Mevcut bir REST API’yi kullanmak veya genişletmek.
GraphQL:
- Karmaşık veri gereksinimleri olan uygulamalar (örneğin, mobil uygulamalar, tek sayfa uygulamaları – SPAs).
- İstemcinin tam olarak hangi verilere ihtiyacı olduğunu bildiği durumlar.
- Farklı istemcilerin (örneğin, web, mobil, masaüstü) aynı API’yi kullandığı durumlar.
- Over-fetching ve under-fetching sorunlarını çözmek.
- Güçlü bir tip sistemi ve otomatik dokümantasyon gereksinimi varsa.
Soru 3: Docker ve Kubernetes
Açıklama: Docker ve Kubernetes nedir? Ne işe yararlar? Aralarındaki ilişki nedir?
Cevap:
Docker:
Tanım: Uygulamaları, bağımlılıkları (kütüphaneler, çalışma zamanı ortamı, vb.) ile birlikte container adı verilen izole ortamlarda paketlemeyi ve çalıştırmayı sağlayan bir platformdur.
Ne İşe Yarar?
- Uygulama Paketleme: Uygulamanın, çalışması için gereken her şeyi (kod, kütüphaneler, bağımlılıklar) içeren bir Docker image oluşturulur.
- İzolasyon: Container’lar, birbirinden ve ana işletim sisteminden (host OS) izole edilmiştir. Bu, farklı uygulamaların birbirini etkilemesini önler.
- Taşınabilirlik: Docker image’ları, herhangi bir Docker destekli ortamda (geliştiricinin bilgisayarı, test sunucusu, üretim sunucusu, bulut platformu) aynı şekilde çalışır. “Bende çalışıyordu” sorununu ortadan kaldırır.
- Hızlı Dağıtım: Container’lar, çok hızlı bir şekilde başlatılabilir ve durdurulabilir.
- Kaynak Verimliliği: Container’lar, sanal makinelere (VMs) göre daha az kaynak kullanır.
Temel Kavramlar
- Dockerfile: Docker image’ının nasıl oluşturulacağını tanımlayan bir metin dosyasıdır.
- Docker Image: Uygulamanın ve bağımlılıklarının paketlenmiş halidir. Salt okunurdur (read-only).
- Docker Container: Bir Docker image’ının çalışan bir örneğidir (instance).
- Docker Hub: Docker image’larını depolamak ve paylaşmak için kullanılan bir kayıt defteridir (registry).
Kubernetes:
Tanım: Container’ları orkestre eden (yöneten, ölçekleyen, dağıtan, izleyen) bir açık kaynaklı sistemdir. “K8s” olarak da kısaltılır.
Ne İşe Yarar?
- Container Orkestrasyonu: Birden fazla container’ın (örneğin, Docker container’ları) birlikte çalışmasını sağlar.
- Otomatik Ölçekleme (Auto-scaling): Uygulamanın yüküne göre container sayısını otomatik olarak artırır veya azaltır.
- Yüksek Erişilebilirlik (High Availability): Bir container veya sunucu çökerse, Kubernetes otomatik olarak yeni bir container başlatır.
- Otomatik Dağıtım (Automated Rollouts) ve Geri Alma (Rollbacks): Uygulamanın yeni sürümlerini kademeli olarak dağıtır ve bir sorun olursa eski sürüme geri döner.
- Servis Keşfi (Service Discovery) ve Yük Dengeleme (Load Balancing): Container’lar arasındaki iletişimi ve yük dağıtımını yönetir.
- Depolama Yönetimi: Kalıcı depolama (persistent storage) birimlerini yönetir.
- Konfigürasyon Yönetimi: Uygulama konfigürasyonlarını (örneğin, ortam değişkenleri) yönetir.
Temel Kavramlar:
- Pod: Kubernetes’teki en küçük dağıtım birimidir. Bir veya daha fazla container’dan oluşur.
- Node: Pod’ların çalıştığı bir fiziksel veya sanal makinedir.
- Cluster: Birbirine bağlı node’lardan oluşan bir kümedir.
- Service: Pod’lara erişmek için kullanılan bir soyutlamadır (abstraction).
- Deployment: Bir uygulamanın nasıl dağıtılacağını ve ölçekleneceğini tanımlayan bir kaynaktır.
- Namespace: Bir cluster’ı mantıksal bölümlere ayırmak için kullanılır.
Aralarındaki İlişki:
- Docker, uygulamaları container’larda paketlemek ve çalıştırmak için kullanılır.
- Kubernetes, bu container’ları yönetmek (dağıtmak, ölçeklemek, izlemek) için kullanılır.
- Kubernetes, Docker’ın yerine geçmez; Docker ile birlikte çalışır. Kubernetes, Docker (veya diğer container runtime’ları) tarafından oluşturulan container’ları orkestre eder. Docker, container’ları oluşturmak ve çalıştırmak için kullanılan bir araçtır; Kubernetes ise bu container’ları yönetmek için kullanılan daha üst düzey bir araçtır.
Soru 4: CI/CD
Açıklama: Sürekli entegrasyon (Continuous Integration – CI) ve sürekli dağıtım (Continuous Delivery/Deployment – CD) nedir? Ne gibi faydaları vardır? Hangi araçlar kullanılır?
Cevap:
Sürekli Entegrasyon (Continuous Integration – CI):
- Tanım: Geliştiricilerin kod değişikliklerini sık sık (genellikle günde birkaç kez) merkezi bir depoya (örneğin, Git) entegre ettiği (birleştirdiği) bir yazılım geliştirme pratiğidir. Her entegrasyondan sonra, otomatik olarak derleme (build) ve testler (unit tests, integration tests) çalıştırılır.
- Faydaları:
- Hataları erken aşamada tespit etme ve çözme.
- Kod kalitesini artırma.
- Entegrasyon sorunlarını azaltma.
- Daha hızlı geri bildirim (feedback) döngüsü.
- Ekip üyeleri arasındaki işbirliğini artırma.
Sürekli Dağıtım (Continuous Delivery/Deployment – CD):
Tanım: CI’nin bir uzantısıdır. Kod değişiklikleri, otomatik olarak derlenip test edildikten sonra, otomatik olarak bir ortama (örneğin, test, staging, üretim) dağıtılır.
- Continuous Delivery: Kod değişiklikleri, manuel bir onayla üretime dağıtılır.
- Continuous Deployment: Kod değişiklikleri, otomatik olarak üretime dağıtılır (herhangi bir manuel onay olmadan).
Faydaları:
- Daha hızlı ve daha sık yazılım dağıtımı.
- Müşterilere daha hızlı değer sunma.
- Dağıtım riskini azaltma.
- Geri bildirim döngüsünü kısaltma.
- Manuel hataları azaltma.
CI/CD Araçları:
- Jenkins: Açık kaynaklı, popüler bir CI/CD aracıdır.
- GitLab CI/CD: GitLab ile entegre bir CI/CD aracıdır.
- GitHub Actions: GitHub ile entegre bir CI/CD aracıdır.
- Azure DevOps: Microsoft’un CI/CD platformudur.
- CircleCI: Bulut tabanlı bir CI/CD platformudur.
- Travis CI: Bulut tabanlı bir CI/CD platformudur.
- Bamboo: Atlassian’ın (Jira, Bitbucket) CI/CD aracıdır.
- TeamCity: JetBrains tarafından sunulan CI/CD aracıdır.
Beyin Fırtınası ve Senaryo Tabanlı Sorular
Bu kısımda daha çok adayın yaratıcılığını ve problem çözme yeteneğini ölçmek için kullanılır.
Soru 1: Ölçeklenebilirlik
Açıklama: Bir sosyal medya uygulamasının kullanıcı sayısı aniden 10 kat artarsa, sistemde ne gibi sorunlar yaşanabilir? Bu sorunların üstesinden gelmek için hangi çözümleri önerirsiniz?
Cevap:
Olası Sorunlar:
- Veritabanı Yükü: Veritabanı, artan okuma ve yazma isteklerini karşılamakta zorlanabilir. Sorgular yavaşlayabilir, zaman aşımları (timeout) yaşanabilir ve hatta veritabanı çökebilir.
- Sunucu Yükü: Uygulama sunucuları (web sunucuları, API sunucuları), artan trafik nedeniyle aşırı yüklenebilir. Yanıt süreleri uzayabilir ve sunucular çökebilir.
- Ağ Tıkanıklığı: Ağ bant genişliği yetersiz kalabilir, bu da gecikmelere (latency) ve paket kayıplarına neden olabilir.
- Önbellek (Cache) Sorunları: Önbellek, artan yükü karşılayacak şekilde yapılandırılmamışsa, verimsiz hale gelebilir veya çökebilir.
- Kaynak Tükenmesi: CPU, RAM, disk alanı gibi sistem kaynakları tükenebilir.
- Üçüncül Taraf Servislerin Limitleri: Uygulamanın kullandığı üçüncü taraf servislerin (örneğin, e-posta gönderme servisi, ödeme ağ geçidi) limitleri aşılabilir.
- Eş Zamanlı Kullanıcı Sayısı: Sistem, aynı anda çok sayıda kullanıcıyı destekleyecek şekilde tasarlanmamış olabilir.
- Loglama ve İzleme (Monitoring): Artan log kayıtları, loglama sistemini aşırı yükleyebilir ve izleme araçları, artan veri miktarı nedeniyle verimsiz hale gelebilir.
Çözümler:
Yatay Ölçekleme (Horizontal Scaling):
- Uygulama sunucularının sayısını artırarak yükü dağıtmak.
- Veritabanını birden fazla sunucuya bölmek (sharding) veya replikasyon (replication) kullanmak.
- Yük dengeleyiciler (load balancer) kullanarak trafiği birden fazla sunucu arasında dağıtmak.
Dikey Ölçekleme (Vertical Scaling)
- Mevcut sunucuların donanım kaynaklarını (CPU, RAM, disk) artırmak (daha güçlü sunuculara geçmek).
Önbellekleme (Caching):
- Sık erişilen verileri önbelleğe alarak veritabanı ve sunucu yükünü azaltmak. Redis, Memcached gibi önbellekleme sistemleri kullanılabilir.
- CDN (Content Delivery Network) kullanarak statik içerikleri (resimler, videolar, CSS, JavaScript dosyaları) kullanıcılara daha yakın sunuculardan dağıtmak.
Asenkron İşlemler:
Zaman alan işlemleri (örneğin, e-posta gönderme, bildirim gönderme, resim işleme) arka planda asenkron olarak yapmak için mesaj kuyrukları (message queues) kullanmak (örneğin, RabbitMQ, Kafka, Azure Service Bus).
Veritabanı Optimizasyonu:
- SQL sorgularını optimize etmek.
- İndeksleri doğru kullanmak.
- Veritabanı bağlantı havuzlarını (connection pools) kullanmak.
- Okuma ve yazma işlemlerini farklı veritabanlarına yönlendirmek (read replicas kullanmak).
- Gerektiğinde NoSQL veritabanları gibi alternatif veri depolama çözümlerini değerlendirmek.
Ağ Optimizasyonu:
- Daha yüksek bant genişliğine sahip bir ağ altyapısı kullanmak.
- İstekleri ve yanıtları sıkıştırmak (örneğin, GZIP).
Kod Optimizasyonu:
- Verimsiz algoritmaları ve kod parçalarını optimize etmek.
- Gereksiz döngülerden ve hesaplamalardan kaçınmak.
- Bellek sızıntılarını (memory leaks) gidermek.
Üçüncül Taraf Servislerin Limitlerini Yönetmek:
- Üçüncül servis sağlayıcılarla iletişime geçip limit artırımı talep etmek.
- Gerekirse, alternatif servis sağlayıcıları değerlendirmek.
İzleme ve Uyarı Sistemleri:
- Sistemin performansını ve sağlığını sürekli olarak izlemek için gelişmiş izleme araçları (örneğin, Prometheus, Grafana, New Relic, Datadog) kullanmak.
- Anormal durumlar (örneğin, yüksek CPU kullanımı, yavaş yanıt süreleri, artan hata oranları) için uyarılar (alerts) tanımlamak.
Yük Testleri (Load Testing):
Sistemi, beklenen yük altında test etmek ve darboğazları (bottlenecks) önceden tespit etmek için düzenli olarak yük testleri yapmak.
Otomatik Ölçekleme (Autoscaling):
Bulut platformlarının (örneğin, AWS, Azure, Google Cloud) sunduğu otomatik ölçekleme özelliklerini kullanarak, sistem kaynaklarını (sunucu sayısı, veritabanı boyutu vb.) otomatik olarak artırmak veya azaltmak.
Soru 2: Optimizasyon
Açıklama: Bir web sayfasının yüklenme süresini azaltmak için neler yaparsınız?
Cevap:
Resimleri Optimize Etmek:
- Resimleri sıkıştırmak (lossy veya lossless compression).
- Doğru resim formatını kullanmak (JPEG, PNG, WebP, SVG).
- srcset ve <picture> elementlerini kullanarak farklı ekran boyutları ve çözünürlükleri için uygun resim boyutlarını sunmak (responsive images).
- Lazy loading (tembel yükleme) kullanarak, resimleri yalnızca kullanıcı sayfayı kaydırdığında veya resim görünür olduğunda yüklemek.
- Tarayıcı önbelleklemesinden (browser caching) yararlanmak için uygun HTTP başlıklarını (cache headers) kullanmak.
CSS ve JavaScript’i Küçültmek (Minify) ve Birleştirmek (Concatenate):
- CSS ve JavaScript dosyalarındaki gereksiz karakterleri (boşluklar, yorumlar) kaldırarak dosya boyutunu küçültmek (minification).
- Birden fazla CSS veya JavaScript dosyasını tek bir dosyada birleştirerek HTTP istek sayısını azaltmak (concatenation).
Tarayıcı Önbelleklemesini (Browser Caching) Kullanmak:
- Statik dosyalar (resimler, CSS, JavaScript) için uygun Cache-Control ve Expires HTTP başlıklarını ayarlayarak, tarayıcının bu dosyaları önbelleğe almasını ve tekrar tekrar indirmemesini sağlamak.
CDN (Content Delivery Network) Kullanmak:
- Statik içerikleri (resimler, CSS, JavaScript) kullanıcılara daha yakın sunuculardan dağıtarak gecikmeyi (latency) azaltmak.
GZIP Sıkıştırması Kullanmak:
- Sunucu tarafında GZIP sıkıştırmasını etkinleştirerek, HTML, CSS ve JavaScript dosyalarının boyutunu küçültmek ve daha hızlı indirilmesini sağlamak.
Render-Blocking Kaynakları Ertelemek veya Asenkron Yüklemek:
- Sayfanın ilk yüklenmesinde gerekli olmayan JavaScript kodlarını ertelemek (defer attribute) veya asenkron olarak yüklemek (async attribute). Bu, sayfanın daha hızlı görüntülenmesini sağlar.
- CSS’i kritik (critical) ve kritik olmayan (non-critical) olarak ayırmak. Kritik CSS’i doğrudan <head> bölümüne eklemek (inline), kritik olmayan CSS’i ise asenkron olarak yüklemek.
HTTP/2 veya HTTP/3 Kullanmak:
- HTTP/2 ve HTTP/3, tek bir TCP bağlantısı üzerinden birden fazla isteği paralel olarak göndermeyi (multiplexing) destekler. Bu, HTTP/1.1’e göre daha hızlı sayfa yüklenmesi sağlar.
Veritabanı Sorgularını Optimize Etmek:
- Yavaş çalışan veritabanı sorgularını tespit etmek ve optimize etmek.
- İndeksleri doğru kullanmak.
Sunucu Yanıt Süresini (Server Response Time) İyileştirmek:
- Daha hızlı bir sunucu kullanmak.
- Sunucu tarafı önbellekleme (server-side caching) kullanmak.
- Sunucu yazılımını (örneğin, web sunucusu, veritabanı sunucusu) optimize etmek.
- Arka planda çalışan gereksiz işlemleri azaltmak/optimize etmek.
Ağ İsteği Sayısını Azaltmak:
- Mümkün olduğunca az sayıda HTTP isteği yapmak.
- CSS sprite’ları kullanarak birden fazla küçük resmi tek bir resim dosyasında birleştirmek.
- Küçük CSS veya JavaScript dosyalarını doğrudan HTML içine gömmek (inline).
Web Yazı Tiplerini (Web Fonts) Optimize Etmek:
- Yalnızca gerekli yazı tipi karakterlerini yüklemek (subsetting).
- font-display CSS özelliğini kullanarak yazı tiplerinin yüklenmesi sırasında metnin nasıl görüntüleneceğini kontrol etmek.
- Yazı tiplerini önceden yüklemek (preload).
Üçüncü Taraf Komut Dosyalarını (Third-Party Scripts) Optimize Etmek:
- Üçüncü taraf komut dosyalarının (örneğin, reklamlar, analiz araçları) sayfa yükleme süresini nasıl etkilediğini analiz etmek.
- Gereksiz komut dosyalarını kaldırmak.
- Komut dosyalarını asenkron olarak yüklemek veya ertelemek.
Mobil Öncelikli (Mobile-First) Tasarım:
- Sayfayı öncelikle mobil cihazlar için tasarlamak ve daha sonra daha büyük ekranlara uyarlamak. Bu, mobil cihazlarda daha hızlı sayfa yüklenmesi sağlar.
Hız Testi Araçlarını Kullanmak:
- Google PageSpeed Insights, WebPageTest, GTmetrix gibi araçları kullanarak sayfa yükleme süresini analiz etmek ve iyileştirme önerileri almak.
Soru 3: Yeni Bir Teknoloji
Açıklama: Yeni bir programlama dili, framework veya teknoloji öğrenmeniz gerektiğinde nasıl bir yaklaşım izlersiniz? Hangi kaynakları kullanırsınız? Öğrenme sürecinizi nasıl planlarsınız?
Cevap:
- Hedef Belirleme: Öncelikle, bu yeni teknolojiyi neden öğrenmem gerektiğini ve bu teknolojiyle ne yapmayı planladığımı netleştiririm. Örneğin, yeni bir iş gereksinimi mi, kişisel bir proje mi, yoksa sadece merak mı? Bu, motivasyonumu yüksek tutmama ve öğrenme sürecimi daha odaklı hale getirmeme yardımcı olur.
- Temel Kaynakları Araştırma:
- Resmi Dokümantasyon: Teknolojinin resmi web sitesindeki dokümantasyonu incelerim. Bu, genellikle en güvenilir ve güncel bilgiyi sağlar.
- Kitaplar: Konuyla ilgili saygın yayınevleri tarafından yayınlanmış kitapları araştırırım.
- Online Kurslar: Udemy, Coursera, Pluralsight, edX gibi platformlardaki online kurslara katılırım.
- Eğitim Videoları: YouTube gibi platformlardaki eğitim videolarını izlerim.
- Blog Yazıları ve Makaleler: Konuyla ilgili uzmanlar tarafından yazılmış blog yazılarını ve makaleleri okurum.
- Podcast’ler: Konuyla ilgili podcast’leri dinlerim.
- Örnek Projeler ve Açık Kaynak Kod: GitHub gibi platformlarda, ilgili teknolojiyle geliştirilmiş örnek projeleri ve açık kaynak kodlu projeleri incelerim.
Öğrenme Planı Oluşturma:
- Temel Kavramları Öğrenme: İlk olarak, teknolojinin temel kavramlarını ve terminolojisini öğrenmeye odaklanırım.
- “Hello, World!” Uygulaması: Basit bir “Hello, World!” uygulaması yazarak, teknolojinin temel sözdizimini (syntax) ve çalışma mantığını anlamaya çalışırım.
- Küçük Projeler: Küçük, pratik projeler yaparak öğrendiklerimi pekiştiririm. Örneğin, basit bir to-do list uygulaması, bir hesap makinesi veya bir blog uygulaması geliştirebilirim.
- Aşamalı Öğrenme: Konuları aşamalı olarak öğrenirim. Temel kavramları öğrendikten sonra, daha karmaşık konulara geçerim.
- Düzenli Pratik: Düzenli olarak pratik yaparım. Her gün veya haftanın belirli günleri kod yazmaya zaman ayırırım.
- Not Alma: Öğrendiğim önemli bilgileri not alırım. Bu notlar, daha sonra tekrar başvurabileceğim bir kaynak olur.
Topluluklara Katılma:
- Forumlar ve Tartışma Grupları: Stack Overflow, Reddit gibi platformlardaki ilgili forumlara ve tartışma gruplarına katılırım. Sorular sorar, cevaplar veririm ve diğer geliştiricilerle etkileşimde bulunurum.
- Sosyal Medya: Twitter, LinkedIn gibi sosyal medya platformlarında ilgili kişileri ve grupları takip ederim.
- Konferanslar ve Etkinlikler: İlgili teknolojiyle ilgili konferanslara ve etkinliklere katılırım (mümkünse).
- Yerel Meetup’lar: Bölgemdeki ilgili meetup’lara katılırım.
Gerçek Dünya Projelerinde Kullanma:
- Mümkünse, öğrendiğim teknolojiyi gerçek bir projede kullanmaya çalışırım. Bu, öğrenme sürecimi hızlandırır ve öğrendiklerimi pekiştirmemi sağlar.
- Kişisel projelerimde veya açık kaynaklı projelere katkıda bulunarak deneyim kazanabilirim.
Sürekli Öğrenme
- Teknoloji sürekli geliştiği için, öğrenme sürecimi sürekli devam ettiririm. Yeni özellikler ve en iyi uygulamalar (best practices) hakkında bilgi sahibi olmak için düzenli olarak kaynakları takip ederim.
10. Kodlama Mülakatları (Live Coding)
Bu bölümde, canlı kodlama mülakatlarında (live coding interviews) sıkça sorulan problem türlerine ve bu problemleri C# ile çözme yaklaşımlarına örnekler verilecektir.
Soru 1: İki Sayının Toplamı (Two Sum)
- Açıklama: Verilen bir tamsayı dizisinde (array), toplamları belirli bir hedef sayıya (target) eşit olan iki sayının indekslerini bulun. Her girdinin tam olarak bir çözümü olduğunu ve aynı elemanı iki kez kullanamayacağınızı varsayın.
Girdi: nums = [2, 7, 11, 15], target = 9 Çıktı: [0, 1] (Çünkü nums[0] + nums[1] == 9) Girdi: nums = [3, 2, 4], target = 6 Çıktı: [1, 2] Girdi: nums = [3, 3], target = 6 Çıktı: [0, 1]
C# Çözümü (Dictionary Kullanarak – Optimal Çözüm):
public static int[] TwoSum(int[] nums, int target)
{
Dictionary<int, int> numMap = new Dictionary<int, int>(); // <sayı, indeks>
for (int i = 0; i < nums.Length; i++)
{
int complement = target - nums[i];
if (numMap.ContainsKey(complement))
{
return new int[] { numMap[complement], i };
}
//Eğer o anda aradığım sayı (complement) yoksa o anki sayıyı ve index'ini ekle
numMap[nums[i]] = i; // Sayıyı ve indeksini dictionary'e ekle
}
return null; // Çözüm bulunamazsa null döndür (veya bir exception fırlat)
}
Çözümün Açıklaması:
- numMap adında bir Dictionary<int, int> oluşturulur. Bu dictionary, dizideki sayıları ve bu sayıların indekslerini saklamak için kullanılır. Key (anahtar) sayı, value (değer) ise o sayının indeksidir.
- for döngüsü ile dizinin elemanları üzerinde gezilir.
- Her bir sayı (nums[i]) için, target değerinden bu sayı çıkarılarak “tamamlayıcı” (complement) sayı bulunur.
- numMap.ContainsKey(complement) ile, tamamlayıcı sayının daha önce dictionary’e eklenip eklenmediği kontrol edilir. Eğer tamamlayıcı sayı dictionary’de varsa, bu, toplamları target değerine eşit olan iki sayının bulunduğu anlamına gelir. Bu durumda, tamamlayıcı sayının indeksi (numMap[complement]) ve o anki sayının indeksi (i) bir dizi olarak döndürülür. Eğer tamamlayıcı sayı dictionary’de yoksa, o anki sayı (nums[i]) ve indeksi (i) dictionary’e eklenir (numMap[nums[i]] = i;).
- Eğer döngü tamamlandığında çözüm bulunamazsa, null döndürülür (veya bir exception fırlatılabilir).
- Zaman Karmaşıklığı: O(n) – Dizinin elemanları üzerinde sadece bir kez döngü yapılır. Dictionary’deki ContainsKey ve ekleme işlemleri ortalama O(1) zaman alır.
- Alan Karmaşıklığı: O(n) – En kötü durumda, dizideki tüm elemanlar dictionary’e eklenir.
C# Çözümü (Brute Force – Optimize Edilmemiş):
public static int[] TwoSumBruteForce(int[] nums, int target)
{
for (int i = 0; i < nums.Length; i++)
{
for (int j = i + 1; j < nums.Length; j++)
{
if (nums[i] + nums[j] == target)
{
return new int[] { i, j };
}
}
}
return null; // Çözüm bulunamazsa
}
Çözümün Açıklaması
- İç içe iki döngü (for) kullanılarak dizideki tüm olası sayı çiftleri denenir.
- Dış döngü i indeksiyle, iç döngü ise j indeksiyle (i+1’den başlayarak) elemanları dolaşır.
- Her bir sayı çiftinin toplamı (nums[i] + nums[j]) target değerine eşitse, bu sayıların indeksleri (i ve j) bir dizi olarak döndürülür.
- Eğer döngüler tamamlandığında çözüm bulunamazsa, null döndürülür.
- Zaman Karmaşıklığı: O(n^2) – İç içe iki döngü kullanıldığı için zaman karmaşıklığı kareseldir (quadratic). Büyük dizilerde bu yöntem çok yavaş çalışır.
- Alan Karmaşıklığı: O(1) – Ekstra bir veri yapısı kullanılmadığı için alan karmaşıklığı sabittir.
Soru 2: String Ters Çevirme (Reverse String)
- Açıklama: Verilen bir string’i ters çeviren bir fonksiyon yazın.
- C# Çözümü (Çeşitli Yaklaşımlar):
- Yöntem 1: Array.Reverse() (En Kolay):
public static string ReverseString(string s)
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
- Açıklama:
- ToCharArray(): Giriş string’i (s) bir karakter dizisine (char[]) dönüştürür.
- Array.Reverse(charArray): Karakter dizisini ters çevirir (yerinde – in-place).
- new string(charArray): Ters çevrilmiş karakter dizisinden yeni bir string oluşturur ve döndürür.
Yöntem 2: StringBuilder (Performanslı):
public static string ReverseStringWithStringBuilder(string s)
{
StringBuilder reversed = new StringBuilder(s.Length); // Performans için kapasiteyi baştan belirle
for (int i = s.Length - 1; i >= 0; i--)
{
reversed.Append(s[i]);
}
return reversed.ToString();
}
- Açıklama:
- StringBuilder reversed = new StringBuilder(s.Length);: StringBuilder sınıfı, string’leri değiştirmek (ekleme, silme, ters çevirme vb.) için daha verimli bir yöntem sunar. s.Length ile kapasiteyi başlangıçta belirlemek, gereksiz bellek ayırma işlemlerini azaltır.
- for döngüsü: Giriş string’inin (s) sonundan başına doğru döner.
- reversed.Append(s[i]);: Her bir karakter, StringBuilder nesnesinin sonuna eklenir.
- reversed.ToString();: StringBuilder içindeki ters çevrilmiş string, normal bir string’e dönüştürülerek döndürülür.
Yöntem 3: LINQ (Kısa ve Okunaklı):
public static string ReverseStringWithLinq(string s)
{
return new string(s.Reverse().ToArray());
}
- Açıklama:
- s.Reverse(): LINQ’nun Reverse() metodu, string’i ters çevirir (aslında bir IEnumerable<char> döndürür).
- ToArray(): Ters çevrilmiş karakterleri bir diziye (char[]) dönüştürür.
- new string(…): Karakter dizisinden yeni bir string oluşturur.
Yöntem 4: Recursion (Özyineleme) – (Pratikte pek tercih edilmez, öğretici amaçlı):
public static string ReverseStringRecursive(string s)
{
if (string.IsNullOrEmpty(s))
{
return s;
}
return ReverseStringRecursive(s.Substring(1)) + s[0];
}
Açıklama:
- Temel Durum (Base Case): Eğer string boşsa (null veya “”) veya tek bir karakterden oluşuyorsa, doğrudan string’i döndürür.
- Özyinelemeli Adım (Recursive Step): String’in ilk karakteri hariç geri kalanını (s.Substring(1)) özyinelemeli olarak ters çevirir ve ilk karakteri (s[0]) bu ters çevrilmiş string’in sonuna ekler.
- Not: Bu yöntem, büyük string’ler için verimsiz olabilir (stack overflow hatasına yol açabilir) ve genellikle pratik kullanım için önerilmez. Daha çok özyineleme (recursion) kavramını anlamak için öğretici bir örnektir.
Soru 3: En Uzun Artan Alt Dizi (Longest Increasing Subsequence)
- Açıklama: Verilen bir tamsayı dizisinde, en uzun artan alt dizinin uzunluğunu bulun. Alt dizi, orijinal diziden bazı elemanların (veya hiçbirinin) silinmesiyle elde edilen, ancak kalan elemanların sırasının korunması gereken bir dizidir.
Girdi: nums = [10, 9, 2, 5, 3, 7, 101, 18] Çıktı: 4 (En uzun artan alt dizi: [2, 3, 7, 101]) Girdi: nums = [0, 1, 0, 3, 2, 3] Çıktı: 4 (En uzun artan alt dizi: [0, 1, 2, 3]) Girdi: nums = [7, 7, 7, 7, 7, 7, 7] Çıktı: 1 (En uzun artan alt dizi: [7])
C# Çözümü (Dynamic Programming – Dinamik Programlama):
public static int LengthOfLIS(int[] nums)
{
if (nums == null || nums.Length == 0)
{
return 0;
}
int[] dp = new int[nums.Length]; // dp[i]: nums[i] ile biten en uzun artan alt dizinin uzunluğu
int maxLength = 1; // En azından bir elemanlı bir artan alt dizi vardır
for (int i = 0; i < nums.Length; i++)
{
dp[i] = 1; // Her eleman, kendisiyle biten 1 uzunluğunda bir alt dizi oluşturur
for (int j = 0; j < i; j++)
{
if (nums[i] > nums[j]) // Eğer nums[i], nums[j]'den büyükse, bir artan alt dizi oluşturabilirler
{
dp[i] = Math.Max(dp[i], dp[j] + 1); // dp[i]'yi güncelle
}
}
maxLength = Math.Max(maxLength, dp[i]); // Genel maksimum uzunluğu güncelle
}
return maxLength;
}
Çözümün Açıklaması:
- dp Dizisi: dp adında bir tamsayı dizisi oluşturulur. dp[i], nums[i] ile biten en uzun artan alt dizinin uzunluğunu tutar.
- Başlangıç Değerleri: dp dizisinin tüm elemanları 1 olarak başlatılır, çünkü her eleman tek başına 1 uzunluğunda bir artan alt dizi oluşturur. maxLength değişkeni de 1 olarak başlatılır.
- İç İçe Döngüler: Dış döngü (i), dizinin tüm elemanları üzerinde gezinir. İç döngü (j), i’den önceki elemanlar üzerinde gezinir.
- Artan Alt Dizi Kontrolü: Eğer nums[i] > nums[j] ise, bu, nums[j] ile biten bir artan alt dizinin sonuna nums[i] elemanının eklenebileceği anlamına gelir.
- dp[i] Güncellemesi: dp[i] = Math.Max(dp[i], dp[j] + 1); Bu satır, dp[i] değerini günceller. Ya mevcut dp[i] değeri (yani, nums[i] ile biten ve nums[j]’yi içermeyen bir alt dizinin uzunluğu) ya da dp[j] + 1 (yani, nums[j] ile biten en uzun artan alt dizinin uzunluğuna 1 eklenmiş hali) daha büyükse, dp[i] o değere ayarlanır.
- maxLength Güncellemesi: Her i için, maxLength değişkeni, dp[i] ile karşılaştırılarak güncellenir. Bu, tüm dizi boyunca bulunan en uzun artan alt dizinin uzunluğunu takip etmemizi sağlar.
- Sonuç: Döngüler tamamlandığında, maxLength değişkeni, en uzun artan alt dizinin uzunluğunu içerir ve bu değer döndürülür.
- Zaman Karmaşıklığı: O(n^2) – İç içe iki döngü kullanıldığı için.
- Alan Karmaşıklığı: O(n) – dp dizisi için.
C# Çözümü (Binary Search – Daha Optimize):
public static int LengthOfLISBinarySearch(int[] nums) {
if (nums == null || nums.Length == 0) {
return 0;
}
List<int> tails = new List<int>(); // tails[i]: i+1 uzunluğundaki tüm artan alt dizilerin en küçük bitiş değeri
//tails dizisi her zaman artan sırada olacak.
foreach (int num in nums) {
if (tails.Count == 0 || num > tails[tails.Count - 1]) {
// Eğer num, tails'deki tüm elemanlardan büyükse, en uzun artan alt diziye eklenebilir
tails.Add(num);
} else {
// Değilse, num'ın tails içinde ait olduğu yeri binary search ile bul
// ve o indeksteki elemanı num ile değiştir (çünkü daha küçük bir bitiş değeri bulduk)
int left = 0;
int right = tails.Count - 1;
while (left < right)
{
int mid = left + (right - left) / 2; // (left+right)/2 aynı sonucu verir fakat olası bir taşmayı (overflow) engeller
if(tails[mid] < num)
{
left = mid + 1;
}
else
{
right = mid;
}
}
tails[left] = num;
}
}
return tails.Count; // tails'in uzunluğu, en uzun artan alt dizinin uzunluğuna eşittir
}
- Çözümün Açıklaması: tails Listesi: tails adında bir List<int> oluşturulur. tails[i], i+1 uzunluğundaki tüm artan alt dizilerin en küçük bitiş değerini tutar. tails listesi her zaman artan sırada olacaktır.
- Döngü: nums dizisindeki her bir num elemanı için:
- En Sona Ekleme: Eğer tails boşsa veya num, tails’deki son elemandan (tails[tails.Count – 1]) büyükse, num değeri tails listesinin sonuna eklenir. Bu, en uzun artan alt dizinin uzadığı anlamına gelir.
- Binary Search ile Güncelleme: Eğer num, tails’deki son elemandan büyük değilse, num’ın tails listesi içinde ait olduğu yer binary search ile bulunur. Bu yer, num’dan büyük veya eşit olan ilk elemanın indeksidir. Bu indeksteki eleman, num ile değiştirilir. Bu, aynı uzunluktaki bir artan alt dizinin daha küçük bir bitiş değeriyle de elde edilebileceğini gösterir (bu, daha sonra daha uzun bir alt dizi oluşturma potansiyelini artırır).
- Sonuç: Döngü tamamlandığında, tails listesinin uzunluğu, en uzun artan alt dizinin uzunluğuna eşittir.
- Zaman Karmaşıklığı: O(n log n) – Her eleman için bir binary search yapıldığı için.
- Alan Karmaşıklığı: O(n) – En kötü durumda, tails listesi dizideki tüm elemanları içerebilir.
Mülakatlara Nasıl Hazırlanılır?
Algoritma ve Veri Yapıları:
- Temel Kavramları Öğrenin: Array, linked list, stack, queue, tree, graph, hash table gibi temel veri yapılarını ve insertion sort, selection sort, bubble sort, merge sort, quicksort, binary search gibi temel algoritmaları öğrenin.
- Pratik Yapın: LeetCode, HackerRank, Codewars, CodeSignal gibi platformlarda bol bol pratik yapın. Farklı zorluk seviyelerinde sorular çözün.
- Zaman ve Alan Karmaşıklığını Anlayın: Her algoritmanın ve veri yapısının zaman ve alan karmaşıklığını (Big O notation) analiz edebilmeniz önemlidir.
- Farklı Çözüm Yaklaşımları: Bir problemi birden fazla yöntemle çözmeye çalışın (örneğin, recursive ve iterative çözümler).
Sistem Tasarımı:
- Temel Kavramları Öğrenin: Load balancing, caching, database sharding, replication, message queues, CAP theorem gibi sistem tasarımı kavramlarını öğrenin.
- Kaynakları Okuyun: “Grokking the System Design Interview”, “Designing Data-Intensive Applications” gibi kitapları okuyun.
- Örnek Sistemleri İnceleyin: Popüler uygulamaların (örneğin, Twitter, Netflix, Instagram) mimarilerini inceleyin.
- Pratik Yapın: Arkadaşlarınızla veya online platformlarda mock sistem tasarımı mülakatları yapın.
Davranışsal Sorular:
- STAR Metodunu Kullanın: Davranışsal sorulara cevap verirken STAR metodunu (Situation, Task, Action, Result) kullanın.
- Hikayeler Hazırlayın: Geçmiş deneyimlerinizden örnekler hazırlayın. Başarılarınız, başarısızlıklarınız, zorluklarla nasıl başa çıktığınız, takım çalışması deneyimleriniz gibi konularda hikayeleriniz olsun.
- Dürüst Olun: Dürüst ve samimi cevaplar verin. Abartıdan kaçının.
- Kendinizi Tanıyın: Güçlü ve zayıf yönlerinizi bilin.
Teknik Bilgi:
- Programlama Dili: Mülakata gireceğiniz pozisyon için gerekli olan programlama dilini (bu rehberde C#) iyi bilin. Dilin temel özelliklerini, veri yapılarını, kütüphanelerini ve best practice’lerini öğrenin.
- Framework’ler: İlgili framework’leri (örneğin, ASP.NET Core, Entity Framework Core) öğrenin.
- Araçlar: Gerekli araçları (örneğin, Visual Studio, Git, Docker) kullanmayı bilin.
Mock (Deneme) Mülakatlar:
- Arkadaşlarınızla Pratik Yapın: Arkadaşlarınızla veya meslektaşlarınızla mock mülakatlar yapın.
- Online Platformları Kullanın: Pramp, InterviewBit gibi platformlarda gerçek mülakatlara benzer deneyimler yaşayabilirsiniz.
- Geri Bildirim Alın: Mülakat performansınız hakkında geri bildirim alın ve bu geri bildirimlere göre kendinizi geliştirin.
Şirketi Araştırın:
- Mülakata gireceğiniz şirketin ne iş yaptığını, ürünlerini, hizmetlerini, kültürünü ve değerlerini araştırın.
- Şirketin kullandığı teknolojileri öğrenin.
- Mülakat sürecinin nasıl işlediğini öğrenmeye çalışın.
Soru Sormaya Hazırlanın:
- Mülakatın sonunda size soru sorma fırsatı verilecektir. Bu fırsatı değerlendirin ve şirkete, pozisyona, ekibe veya teknolojiye dair aklınızdaki soruları sorun. Bu, sizin ilgili ve istekli olduğunuzu gösterir.
- Kendinize Güvenin:
Bu rehber, yazılım mülakatlarına hazırlanmanıza yardımcı olacak kapsamlı bir kaynak sunmayı amaçlamaktadır. Düzenli pratik, doğru kaynaklar ve özgüvenle, bu mülakatlarda başarılı olabilir ve kariyerinizde önemli bir adım atabilirsiniz. Başarılar!

No responses yet