نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
المقالات/الخوارزميات
الخوارزميات

Big O: عندما يتجمد السيرفر وأنت تشاهد الـ Event Loop يتوسل للرحمة

هل تساءلت يوماً لماذا يتجمد تطبيقك عند معالجة ١٠ آلاف سجل؟ Big O ليس مجرد نظرية أكاديمية، بل هو مفتاح فهم كيف يتصرف كودك تحت الضغط. سنفكك معاً التعقيدات خلف الكواليس، ونرى كيف يؤثر كل حرف في O(n) على أداء السيرفر، والذاكرة، وحتى فاتورة السحابة.

فريق نوفيل٢٢ أغسطس ٢٠٢٦7 دقائق قراءة٨ مشاهدة

في أحد أيام الجمعة الحارة، كنت أراجع كوداً لإحدى الشركات الناشئة في دبي. الكود بسيط: دالة تحسب مجموع الأرباح الشهرية من قاعدة بيانات تحتوي ٥٠ ألف سجل. لكن عند تشغيله، تجمد السيرفر لمدة ٣٠ ثانية كاملة. المشكلة؟ دالة بسيطة تستخدم loop داخل loop دون أي تفكير في Big O. المبرمج اعتذر قائلاً: "الكود شغال، ما في مشكلة". الحقيقة هي: الكود شغال، لكن الأداء كارثي. هذا المقال ليس عن تعريف Big O، بل عن فهم كيف يؤثر كل حرف في O(n) على حياتك اليومية كمطور، وكيف يمكنك تجنب الكوارث قبل وقوعها.

لنبدأ بسؤال بسيط: لماذا يهتم المطورون بـ Big O؟ لأننا نعيش في عالم مليء بالبيانات. عندما يكون لديك ١٠٠ سجل، لا يهم إذا كانت خوارزميتك O(n²) أو O(n log n). لكن عندما تصل إلى مليون سجل، الفرق بين ١٠ ثوانٍ و١٠ ساعات يصبح كارثياً. Big O يعطينا لغة مشتركة لوصف كيف يتصاعد استهلاك الموارد مع زيادة حجم البيانات. لكن الأهم من ذلك، هو أنه يكشف لنا أين تكمن المشاكل الحقيقية في الكود.

O(1) مقابل O(n): عندما تصبح الثواني ساعات

لنأخذ مثالاً عملياً من مشروع حقيقي. في أحد تطبيقات التجارة الإلكترونية التي عملت عليها، كان لدينا دالة للتحقق من صلاحية كوبون الخصم. النسخة الأولى كانت تستخدم بحث خطي في مصفوفة تحتوي جميع الكوبونات الصالحة. مع نمو عدد الكوبونات، بدأنا نلاحظ تأخيراً ملحوظاً عند تطبيق الخصم. الحل؟ استبدلنا المصفوفة بـ HashMap (أو Object في JavaScript). الفرق؟ من O(n) إلى O(1). لكن لماذا هذا الفرق كبير؟ لأن في O(n)، كل سجل إضافي يعني خطوة إضافية في البحث. أما في O(1)، فالبحث يستغرق نفس الوقت مهما كان حجم البيانات. هذا ليس مجرد تحسين بسيط، بل هو تغيير جذري في كيفية تعامل السيرفر مع الطلبات.

لكن هناك فخ شائع هنا: ليس كل ما يبدو O(1) هو كذلك فعلياً. في JavaScript مثلاً، الوصول إلى عنصر في مصفوفة باستخدام الفهرس هو O(1)، لكن استخدام دالة مثل indexOf() هو O(n). لماذا؟ لأن indexOf() تضطر للبحث عن العنصر خطوة بخطوة. نفس الشيء ينطبق على دوال مثل includes() و find(). لذلك، عندما ترى كوداً يستخدم هذه الدوال داخل loop، فاعلم أنك أمام مشكلة أداء محتملة. الحل؟ استخدم الهياكل المناسبة مثل Sets أو Maps عندما تحتاج إلى عمليات بحث متكررة.

javascript
// مثال سيء: O(n²) بسبب استخدام includes داخل loop
function filterCommonElements(arr1, arr2) {
 return arr1.filter(item => arr2.includes(item));
}

// مثال جيد: O(n) باستخدام Set
function filterCommonElementsOptimized(arr1, arr2) {
 const set2 = new Set(arr2);
 return arr1.filter(item => set2.has(item));
}

// اختبار الأداء
const largeArray1 = Array.from({length: 10000}, (_, i) => i);
const largeArray2 = Array.from({length: 10000}, (_, i) => i + 5000);

console.time('Bad');
filterCommonElements(largeArray1, largeArray2);
console.timeEnd('Bad'); // ~150ms

console.time('Good');
filterCommonElementsOptimized(largeArray1, largeArray2);
console.timeEnd('Good'); // ~2ms

O(n log n): عندما تصبح الـ Sorting كارثة غير متوقعة

في أحد المشاريع التي عملت عليها مع فريق في أمازون، كان لدينا نظام لتصنيف المنتجات حسب الشعبية. النسخة الأولى كانت تستخدم دالة sort بسيطة في JavaScript، والتي تستخدم خوارزمية TimSort خلف الكواليس (تعقيدها O(n log n)). مع ١٠ آلاف منتج، كان كل طلب يستغرق حوالي ٥٠٠ مللي ثانية. لكن عندما وصلنا إلى ١٠٠ ألف منتج، ارتفع الوقت إلى ٦ ثوانٍ كاملة. المشكلة؟ لم نكن نفكر في أن الـ sorting يحدث في كل طلب، حتى لو كانت البيانات نفسها لا تتغير. الحل؟ تخزين النتيجة المرتبة مسبقاً في قاعدة البيانات، وتحديثها فقط عند تغيير البيانات.

لكن هناك فخ أكبر هنا: ليس كل الـ sorting متشابه. في بعض الحالات، قد تضطر لاستخدام خوارزمية مختلفة بناءً على طبيعة البيانات. مثلاً، إذا كانت بياناتك شبه مرتبة مسبقاً، فإن Insertion Sort قد يكون أفضل من QuickSort رغم أن تعقيدها العام O(n²). لماذا؟ لأن الخوارزميات لها ثوابت مخفية في الـ Big O. O(n log n) لا يعني دائماً أداء أفضل من O(n²) مع مجموعات البيانات الصغيرة. لذلك، عندما تختار خوارزمية، لا تعتمد فقط على الـ Big O النظري، بل اختبر الأداء الفعلي مع بياناتك الحقيقية.

python
# مثال على تأثير حجم البيانات على أداء الخوارزميات
import time
import random

def measure_sort_time(data, sort_func):
 start = time.time()
 sort_func(data.copy())
 return time.time() - start

# بيانات عشوائية بحجم مختلف
data_sizes = [1000, 5000, 10000, 50000, 100000]
results = {}

for size in data_sizes:
 data = [random.randint(0, 1000000) for _ in range(size)]
 results[size] = {
 'sorted': measure_sort_time(data, sorted),
 'list_sort': measure_sort_time(data, list.sort)
 }

# طباعة النتائج
for size, times in results.items():
 print(f"Size: {size}")
 print(f" sorted(): {times['sorted']:.6f} seconds")
 print(f" list.sort(): {times['list_sort']:.6f} seconds")
 print(f" Ratio: {times['sorted']/times['list_sort']:.2f}x")

O(n²) و O(2^n): عندما يصبح الكود غير قابل للاستخدام

في أحد المشاريع الحكومية التي عملت عليها، كان لدينا نظام لتوليد جميع المجموعات الممكنة من مجموعة من العناصر. المبرمج كتب دالة تستخدم loop متداخلة لتوليد المجموعات. مع ١٠ عناصر، كان الكود يعمل بشكل جيد. لكن عندما وصلنا إلى ٣٠ عنصر، أصبح الوقت المقدر لتشغيل الدالة هو ١٠ سنوات! لماذا؟ لأن التعقيد كان O(2^n). هذا النوع من الأخطاء ليس مجرد مشكلة أداء، بل هو خطأ منطقي في تصميم الخوارزمية. الحل؟ استخدام خوارزميات أكثر ذكاءً مثل Backtracking أو البرمجة الديناميكية.

لكن هناك مشكلة أكبر: بعض المطورين لا يدركون أن بعض العمليات البسيطة يمكن أن تكون O(n²) دون قصد. مثلاً، في JavaScript، استخدام concat() داخل loop لإنشاء مصفوفة جديدة هو O(n²) لأن كل concat ينشئ مصفوفة جديدة وينسخ جميع العناصر. نفس الشيء ينطبق على دوال مثل splice() و slice() عند استخدامها بشكل غير مدروس. لذلك، عندما ترى كوداً يستخدم هذه الدوال داخل loop، توقف وفكر: هل هناك طريقة أفضل؟ غالباً، الإجابة هي نعم، باستخدام دوال مثل push() و pop() التي تعمل في O(1).

javascript
// مثال سيء: O(n²) بسبب استخدام concat داخل loop
function generateSubsetsBad(arr) {
 let subsets = [[]];
 for (let i = 0; i < arr.length; i++) {
 const temp = [];
 for (let j = 0; j < subsets.length; j++) {
 temp.push(subsets[j].concat(arr[i]));
 }
 subsets = subsets.concat(temp);
 }
 return subsets;
}

// مثال جيد: O(n * 2^n) باستخدام bitmask (أفضل بكثير)
function generateSubsetsGood(arr) {
 const subsets = [];
 const n = arr.length;
 for (let mask = 0; mask < (1 << n); mask++) {
 const subset = [];
 for (let i = 0; i < n; i++) {
 if (mask & (1 << i)) {
 subset.push(arr[i]);
 }
 }
 subsets.push(subset);
 }
 return subsets;
}

// اختبار الأداء
const smallArray = [1, 2, 3];
const largeArray = Array.from({length: 20}, (_, i) => i);

console.time('Bad - Small');
generateSubsetsBad(smallArray);
console.timeEnd('Bad - Small'); // ~0.1ms

console.time('Good - Small');
generateSubsetsGood(smallArray);
console.timeEnd('Good - Small'); // ~0.05ms

// لا نقوم بتشغيل Bad على largeArray لأنه سيستغرق وقتاً طويلاً جداً
console.time('Good - Large');
generateSubsetsGood(largeArray);
console.timeEnd('Good - Large'); // ~100ms

الذاكرة أيضاً لها Big O: عندما يتحول الـ Memory Leak إلى كابوس

Big O لا يقتصر على الوقت فقط، بل يشمل أيضاً استهلاك الذاكرة. في أحد المشاريع التي عملت عليها مع فريق في أوبر، كان لدينا نظام لمعالجة الرحلات في الوقت الفعلي. مع زيادة عدد المستخدمين، بدأنا نلاحظ أن الذاكرة المستخدمة ترتفع بشكل غير متناسب مع عدد الرحلات. المشكلة؟ كنا نخزن جميع الرحلات في مصفوفة، ثم نستخدم دوال مثل filter و map التي تنشئ مصفوفات جديدة في كل مرة. هذا يعني أن استهلاك الذاكرة كان O(n²) في أسوأ الحالات. الحل؟ استخدام Generators و Iterators بدلاً من المصفوفات عند الإمكان، وتجنب تخزين البيانات غير الضرورية في الذاكرة.

لكن هناك مشكلة أكبر: بعض المطورين لا يدركون أن بعض الهياكل البيانية تستهلك ذاكرة أكثر من غيرها. مثلاً، في Python، استخدام Dictionary قد يبدو خياراً جيداً للبحث السريع، لكن إذا كانت المفاتيح عبارة عن strings طويلة، فإن استهلاك الذاكرة قد يكون كبيراً جداً. نفس الشيء ينطبق على استخدام Lists بدلاً من Tuples للبيانات الثابتة. لذلك، عندما تختار هيكل البيانات، فكر في كل من الوقت والذاكرة. أحياناً، قد تضطر للتضحية ببعض السرعة للحصول على ذاكرة أقل، أو العكس، بناءً على متطلبات المشروع.

python
# مثال على تأثير اختيار هيكل البيانات على الذاكرة
import sys
import random
import string

# إنشاء بيانات عشوائية
random_strings = [''.join(random.choices(string.ascii_letters, k=20)) for _ in range(10000)]
random_values = [random.randint(0, 1000) for _ in range(10000)]

# استخدام Dictionary
string_dict = {k: v for k, v in zip(random_strings, random_values)}
dict_size = sys.getsizeof(string_dict)
for k, v in string_dict.items():
 dict_size += sys.getsizeof(k) + sys.getsizeof(v)

# استخدام List of Tuples
string_list = list(zip(random_strings, random_values))
list_size = sys.getsizeof(string_list)
for item in string_list:
 list_size += sys.getsizeof(item) + sys.getsizeof(item[0]) + sys.getsizeof(item[1])

print(f"Dictionary size: {dict_size / (1024 * 1024):.2f} MB")
print(f"List of Tuples size: {list_size / (1024 * 1024):.2f} MB")
print(f"Ratio: {dict_size / list_size:.2f}x")

كيف تقرأ Big O مثل المحترفين؟

عندما ترى تعبير Big O، لا تقرأ الحروف فقط، بل فكر في ما تعنيه خلف الكواليس. مثلاً، O(n log n) لا يعني "أسرع من O(n²)"، بل يعني "عندما يتضاعف حجم البيانات، يتضاعف الوقت تقريباً مع عامل إضافي صغير". هذا الفهم العميق يساعدك على اتخاذ قرارات أفضل في التصميم. مثلاً، إذا كنت تعلم أن خوارزمية معينة هي O(n log n)، فقد تقرر أنها جيدة بما يكفي لمشروعك الحالي، بدلاً من إضاعة الوقت في محاولة تحسينها إلى O(n).

لكن هناك نقطة مهمة: Big O لا يخبرك بالقصة الكاملة. هناك ثوابت مخفية في كل خوارزمية. مثلاً، خوارزمية O(n) قد تكون أبطأ من خوارزمية O(n²) مع مجموعات البيانات الصغيرة بسبب هذه الثوابت. لذلك، عندما تختار بين خوارزميتين، لا تعتمد فقط على الـ Big O، بل اختبر الأداء الفعلي مع بياناتك الحقيقية. في النهاية، ما يهم هو كيف يتصرف الكود في العالم الحقيقي، وليس في النظرية.

  • •O(1): الوقت ثابت بغض النظر عن حجم البيانات. مثال: الوصول إلى عنصر في مصفوفة باستخدام الفهرس.
  • •O(log n): الوقت يزيد ببطء مع زيادة حجم البيانات. مثال: البحث الثنائي في مصفوفة مرتبة.
  • •O(n): الوقت يزيد خطياً مع حجم البيانات. مثال: البحث الخطي في مصفوفة غير مرتبة.
  • •O(n log n): الوقت يزيد بشكل شبه خطي. مثال: خوارزميات الفرز الفعالة مثل Merge Sort و QuickSort.
  • •O(n²): الوقت يزيد بشكل مربع مع حجم البيانات. مثال: خوارزميات الفرز البسيطة مثل Bubble Sort.
  • •O(2^n): الوقت يتضاعف مع كل عنصر إضافي. مثال: خوارزميات توليد المجموعات الفرعية.
  • •O(n!): الوقت يزيد بشكل عاملي. مثال: خوارزميات حل مسألة البائع المتجول بطريقة القوة الغاشمة.

خلاصة المهندس: نصيحة واحدة تغير كل شيء

إذا أخذت شيئاً واحداً من هذا المقال، فليكن هذا: لا تكتب كوداً ثم تفكر في الأداء. فكر في الأداء قبل أن تكتب الكود. عندما تبدأ في كتابة دالة جديدة، اسأل نفسك: ما هو تعقيد هذه الدالة؟ هل هناك طريقة لجعلها أسرع؟ هل هناك هيكل بيانات أفضل يمكنني استخدامه؟ هذه العادة البسيطة ستوفر عليك ساعات من تصحيح الأخطاء وتحسين الأداء لاحقاً. تذكر: الكود الجيد ليس الذي يعمل فقط، بل الذي يعمل بكفاءة حتى مع زيادة حجم البيانات. Big O ليس مجرد نظرية أكاديمية، بل هو أداة عملية تساعدك على كتابة كود أفضل، وسيرفرات أسرع، وتطبيقات أكثر استقراراً. ابدأ في استخدامها اليوم، وستشكر نفسك غداً.

Big O أداء الكود تحسين الخوارزميات البرمجة العملية هياكل البيانات

التعليقات

العودة للمقالات
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر