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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر

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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر

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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر

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

Big O: الدليل العملي الذي سيفتح عينيك على ما يحدث خلف الكواليس

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

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

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

Big O هي لغة المهندسين للحديث عن الأداء. عندما يقول أحدهم "هذا الكود O(n)"، فهو يخبرك أن الوقت الذي يستغرقه الكود يتناسب طردياً مع حجم المدخلات. لكن الأهم من ذلك هو ما لا يخبرك به: كيف يتصرف الكود تحت ضغط الـ Memory و الـ CPU Cache و الـ I/O Bound Operations. في هذا الدليل، سنفكك Big O من منظور عملي، مع أمثلة حقيقية من مشاريع الإنتاج، وأخطاء شائعة تجعل حتى المطورين السنيين يقعوا في فخاخ الأداء.

Big O ليست مجرد أرقام: إنها قصة ما يحدث داخل المعالج

عندما نتحدث عن O(1) أو O(n)، فإننا في الواقع نتحدث عن عدد العمليات الأساسية التي يقوم بها المعالج. لكن هذه العمليات ليست متساوية في التكلفة. قراءة قيمة من الـ Cache أسرع بـ ١٠٠ مرة من قراءة نفس القيمة من الـ RAM، وأسرع بـ ١٠٠٠٠ مرة من قراءة نفس القيمة من القرص الصلب. هذا يعني أن خوارزمية O(n) قد تكون أسرع من خوارزمية O(1) إذا كانت الأولى تستخدم الـ Cache بكفاءة أكبر.

لنأخذ مثالاً عملياً: عند البحث في مصفوفة مرتبة، يمكن استخدام Binary Search التي هي O(log n). لكن إذا كانت المصفوفة صغيرة جداً (مثلاً ١٠ عناصر)، فإن Linear Search التي هي O(n) قد تكون أسرع لأن Binary Search تتطلب عدة عمليات قفز عشوائية في الذاكرة، مما يؤدي إلى Misses في الـ Cache. هذا هو السبب في أن مكتبات مثل C++ STL تستخدم Linear Search للبحث في الـ Small Arrays بدلاً من Binary Search رغم أن الأخيرة أفضل من حيث Big O.

cpp
#include <vector>
#include <algorithm>
#include <chrono>

// Binary Search vs Linear Search on small arrays
int main() {
 std::vector<int> smallArray = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
 int target = 19;
 
 // Linear Search (O(n))
 auto start = std::chrono::high_resolution_clock::now();
 auto it = std::find(smallArray.begin(), smallArray.end(), target);
 auto end = std::chrono::high_resolution_clock::now();
 auto linearTime = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
 
 // Binary Search (O(log n))
 start = std::chrono::high_resolution_clock::now();
 it = std::lower_bound(smallArray.begin(), smallArray.end(), target);
 end = std::chrono::high_resolution_clock::now();
 auto binaryTime = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
 
 // On small arrays, Linear Search is often faster due to cache locality
 std::cout << "Linear Search: " << linearTime << " ns\n";
 std::cout << "Binary Search: " << binaryTime << " ns\n";
 
 return 0;
}

عندما يتحول O(n) إلى كابوس O(n²): قصة الـ Nested Loops الخفية

أكثر الأخطاء شيوعاً التي أراها في الكود هي الـ Nested Loops التي تبدو بريئة في البداية، لكنها تتحول إلى كابوس عندما تكبر البيانات. المثال الكلاسيكي هو التحقق من التكرارات في مصفوفة. الكود التالي يبدو بسيطاً، لكنه في الواقع O(n²):

javascript
// Bad: O(n²) approach to find duplicates
function hasDuplicates(arr) {
 for (let i = 0; i < arr.length; i++) {
 for (let j = i + 1; j < arr.length; j++) {
 if (arr[i] === arr[j]) {
 return true;
 }
 }
 }
 return false;
}

// Good: O(n) approach using a Set
function hasDuplicatesOptimized(arr) {
 const seen = new Set();
 for (const item of arr) {
 if (seen.has(item)) {
 return true;
 }
 seen.add(item);
 }
 return false;
}

لكن المشكلة الحقيقية ليست في هذا المثال البسيط، بل في الـ Nested Loops الخفية التي تظهر في الكود المعقد. مثلاً، في مشروع سابق، كان لدينا نظام معالجة طلبات يستخدم Map لتقسيم الطلبات حسب المنطقة. الكود كان يبدو O(n) في البداية، لكن عند التحليل الدقيق، تبين أن كل عملية insert في الـ Map كانت تستدعي دالة مقارنة مخصصة كانت تحتوي على loop داخلي. النتيجة؟ O(n²) دون أن ندري. الحل كان استخدام Hash Map مع دالة هاش بسيطة بدلاً من الـ Map التي تستخدم المقارنة.

كيف تكشف الـ Nested Loops الخفية في الكود الخاص بك

  • •ابحث عن دوال مثل map, filter, reduce التي تستدعي دوال أخرى داخلها. إذا كانت الدالة الداخلية تحتوي على loop أو عملية مكلفة، فأنت أمام nested loop خفي.
  • •افحص دوال المقارنة (comparators) في الـ Sorting أو الـ Tree Structures. إذا كانت الدالة تقوم بأي عملية غير O(1)، فأنت تزيد من تعقيد الخوارزمية.
  • •استخدم أدوات الـ Profiling مثل Chrome DevTools أو Py-Spy لتتبع الوقت المستغرق في كل دالة. إذا رأيت أن الوقت يزيد بشكل مربع مع حجم البيانات، فابحث عن الـ Nested Loops.
  • •انتبه للـ Recursion. دالة recursive بسيطة قد تبدو O(n)، لكنها في الواقع O(n²) إذا كانت تستدعي نفسها مرتين في كل مرة (مثل بعض تطبيقات Fibonacci).

Big O في عالم الـ Real-World: عندما لا تكفي النظرية

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

لنأخذ مثالاً من مشروع حقيقي: كنا نعمل على نظام معالجة صور يستخدم FFT (Fast Fourier Transform) لتحليل الصور. في النظرية، FFT هو O(n log n)، وهو أفضل بكثير من الـ O(n²) للخوارزميات التقليدية. لكن عندما طبقنا FFT على صور صغيرة (أقل من ٦٤x٦٤ بكسل)، وجدنا أن الخوارزمية التقليدية كانت أسرع. السبب؟ الـ Overhead الخاص بتحضير البيانات وتنفيذ الـ Recursion في FFT كان يفوق الفائدة من الـ O(n log n) عندما تكون n صغيرة.

python
import numpy as np
import time

# Comparing FFT vs Direct Convolution on small images
def direct_convolution(image, kernel):
 # O(n²) direct convolution
 output = np.zeros_like(image)
 k_center = kernel.shape[0] // 2
 for i in range(image.shape[0]):
 for j in range(image.shape[1]):
 for ki in range(kernel.shape[0]):
 for kj in range(kernel.shape[1]):
 ii = i + ki - k_center
 jj = j + kj - k_center
 if ii >= 0 and ii < image.shape[0] and jj >= 0 and jj < image.shape[1]:
 output[i, j] += image[ii, jj] * kernel[ki, kj]
 return output

# Small image (32x32) and kernel (3x3)
image = np.random.rand(32, 32)
kernel = np.random.rand(3, 3)

# Direct Convolution
start = time.time()
result_direct = direct_convolution(image, kernel)
print(f"Direct Convolution: {time.time() - start:.6f} seconds")

# FFT-based Convolution (O(n log n))
start = time.time()
fft_image = np.fft.fft2(image)
fft_kernel = np.fft.fft2(kernel, s=image.shape)
result_fft = np.fft.ifft2(fft_image * fft_kernel).real
print(f"FFT Convolution: {time.time() - start:.6f} seconds")

# On small images, direct convolution is often faster due to FFT overhead

الـ Big O في قواعد البيانات: عندما يتحول O(1) إلى O(n) بسبب الـ I/O

في قواعد البيانات، الـ Big O تأخذ منحى مختلفاً تماماً. عندما تقول أن البحث في جدول هو O(1) لأنك تستخدم index، فأنت تتجاهل حقيقة أن الوصول إلى القرص الصلب هو عملية بطيئة جداً مقارنة بالوصول إلى الذاكرة. في الواقع، حتى الـ O(1) قد يكون بطيئاً جداً إذا كان يتطلب قراءة صفحة كاملة من القرص.

في مشروع سابق، كان لدينا جدول يحتوي على ٥٠ مليون سجل، وكان لدينا index على عمود البريد الإلكتروني. نظرياً، البحث عن مستخدم باستخدام البريد الإلكتروني يجب أن يكون O(1). لكن في الواقع، كان الـ Query يستغرق ٥٠٠ مللي ثانية. السبب؟ الـ Index كان كبيراً جداً بحيث لم يكن يتناسب مع الـ Memory Buffer Pool في قاعدة البيانات. كل مرة كنا نبحث عن مستخدم، كانت قاعدة البيانات تضطر لقراءة صفحة من القرص. الحل؟ استخدمنا الـ Covering Index الذي يحتوي على جميع الأعمدة التي نحتاجها في الـ Query، مما قلل الوقت إلى ٥ مللي ثوانٍ فقط.

sql
-- Bad: Query that causes disk I/O even with an index
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'user@example.com';

-- Good: Covering Index that avoids table access
CREATE INDEX idx_users_email_covering ON users(email) INCLUDE (name, created_at);

EXPLAIN ANALYZE
SELECT name, created_at FROM users WHERE email = 'user@example.com';

كيف تتجنب فخاخ الـ I/O في قواعد البيانات

  • •استخدم الـ Covering Indexes لتجنب الوصول إلى الجدول الرئيسي. إذا كان الـ Index يحتوي على جميع الأعمدة التي تحتاجها في الـ Query، فلن تضطر قاعدة البيانات لقراءة الجدول.
  • •قلل من حجم الـ Indexes. الـ Index الكبير يعني المزيد من الـ I/O. استخدم أعمدة صغيرة (مثل INT بدلاً من VARCHAR) في الـ Indexes.
  • •استخدم الـ Query Cache بحذر. في بعض الحالات، قد يكون الـ Cache أسوأ من الـ I/O المباشر إذا كانت البيانات تتغير باستمرار.
  • •افهم كيف تعمل الـ Buffer Pool في قاعدة البيانات الخاصة بك. في MySQL، يمكنك زيادة حجم الـ innodb_buffer_pool_size لتقليل الـ I/O.
  • •استخدم أدوات مثل EXPLAIN ANALYZE في PostgreSQL أو EXPLAIN في MySQL لفهم كيف تنفذ قاعدة البيانات الـ Query الخاص بك.

Big O في الـ Frontend: عندما يتجمد الـ UI بسبب خوارزمية سيئة

في الـ Frontend، لا نفكر عادة في الـ Big O، لكن الأخطاء هنا تكون مؤلمة جداً للمستخدم. المثال الكلاسيكي هو الـ Rendering في الـ React. إذا كان لديك قائمة تحتوي على ١٠٠٠ عنصر، وكان كل عنصر يعيد الـ Render عند أي تغيير في الـ State، فأنت أمام مشكلة O(n) في أحسن الأحوال، وقد تصل إلى O(n²) إذا كان هناك nested rendering.

في مشروع سابق، كان لدينا جدول بيانات يحتوي على ٥٠٠٠ صف، وكان كل صف يحتوي على عدة مكونات فرعية. عند تغيير قيمة واحدة في الجدول، كان الـ React يعيد رسم الجدول بالكامل، مما يؤدي إلى تجميد الـ UI لمدة ثانيتين. الحل؟ استخدمنا الـ React.memo لمنع إعادة رسم المكونات التي لم تتغير، واستخدمنا الـ Virtual Scrolling لعرض جزء صغير من البيانات فقط. النتيجة؟ زمن الـ Rendering انخفض من O(n) إلى O(1) تقريباً.

javascript
import React, { useState, memo } from 'react';

// Bad: Re-renders all rows when one row changes
const DataTableBad = ({ data }) => {
 const [selectedRow, setSelectedRow] = useState(null);
 
 return (
 <table>
 <tbody>
 {data.map((row, index) => (
 <tr
 key={row.id}
 {() => setSelectedRow(row.id)}
 style={{ backgroundColor: selectedRow === row.id ? '#eee' : 'white' }}
 >
 <td>{row.name}</td>
 <td>{row.value}</td>
 </tr>
 ))}
 </tbody>
 </table>
 );
};

// Good: Uses memo to prevent unnecessary re-renders
const Row = memo(({ row, isSelected, onClick }) => {
 return (
 <tr onClick={onClick} style={{ backgroundColor: isSelected ? '#eee' : 'white' }}>
 <td>{row.name}</td>
 <td>{row.value}</td>
 </tr>
 );
});

const DataTableGood = ({ data }) => {
 const [selectedRow, setSelectedRow] = useState(null);
 
 return (
 <table>
 <tbody>
 {data.map((row) => (
 <Row
 key={row.id}
 row={row}
 isSelected={selectedRow === row.id}
 onClick={() => setSelectedRow(row.id)}
 />
 ))}
 </tbody>
 </table>
 );
};

خلاصة المهندس: نصائح عملية لا تجدها في الكتب

Big O ليست مجرد نظرية تدرسها في الجامعة ثم تنساها. إنها أداة يومية يجب أن تستخدمها في كل سطر تكتبه من الكود. إليك نصائح عملية من تجربتي في الإنتاج:

  • •لا تثق أبداً في الـ Big O النظري. اختبر الكود الخاص بك على بيانات حقيقية باستخدام أدوات مثل Chrome DevTools أو Py-Spy. النظرية تقول أن O(n log n) أفضل من O(n²)، لكن في الواقع قد يكون العكس صحيحاً إذا كانت الـ Constant Factors كبيرة.
  • •انتبه للـ Nested Loops الخفية. استخدم أدوات الـ Profiling لكشفها. إذا رأيت أن زمن التنفيذ يزيد بشكل مربع مع حجم البيانات، فابحث عن الـ Loops المتداخلة.
  • •في قواعد البيانات، الـ I/O هو العدو الحقيقي. استخدم الـ Covering Indexes وقلل من حجم الـ Indexes لتجنب قراءة القرص.
  • •في الـ Frontend، الـ Rendering المفرط هو السبب الرئيسي لتجمد الـ UI. استخدم الـ React.memo و الـ Virtual Scrolling لتحسين الأداء.
  • •لا تنسى الـ Memory. خوارزمية O(n) قد تكون أسوأ من O(n²) إذا كانت تستهلك ذاكرة كبيرة وتؤدي إلى الـ Swapping.
  • •افهم كيف يعمل الـ Hardware. الـ Cache و الـ Branch Prediction و الـ SIMD يمكن أن تغير قواعد اللعبة. خوارزمية O(n) قد تكون أسرع من O(1) إذا كانت تستخدم الـ Cache بكفاءة.

في النهاية، Big O هي لغة للتفكير في الأداء. لكنها ليست كل شيء. الكود الجيد هو الذي يوازن بين الـ Big O و الـ Readability و الـ Maintainability. لكن بدون فهم عميق للـ Big O، ستكتب كوداً يبدو جيداً في التطوير ويتحول إلى كابوس في الإنتاج. ابدأ اليوم بتحليل الكود الذي تكتبه، وابحث عن الـ Bottlenecks قبل أن يجدها المستخدمون.

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

التعليقات

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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر