المستوى:
خبرة متوسطة
عدد الأسئلة:
١٢ سؤالاً
الزمن:
٦ دقائق

أسئلة مقابلة مبرمج لأصحاب الخبرة مع الإجابة

مواقف عملية تُطرح في مقابلات وظيفة مبرمج حاسب آلي لأصحاب الخبرة المتوسطة: أساسيات البرمجة، والخوارزميات، وهياكل البيانات، والبرمجة كائنية التوجه، وقواعد البيانات وSQL، وتصحيح الأخطاء وGit. في آخرها تقرير بنقاط ضعفك.

المهارات: أساسيات البرمجة والمنطق، البرمجة كائنية التوجه، هياكل البيانات، الخوارزميات وحل المشكلات، تصحيح الأخطاء والاختبار وGit، قواعد البيانات وSQL

تعليمات: لكل سؤال إجابة صحيحة واحدة. إن لم تعرف الإجابة فاختر «لا أعرف»، وسيدلّك التقرير على ما تراجعه.

السؤال الأول: ماذا تطبع شيفرة C# التالية؟ static void Change(int[] arr, int n) { arr[0] = 99; n = 99; arr = new int[] { 0 }; } // inside Main int[] a = { 1, 2 }; int x = 1; Change(a, x); Console.WriteLine(a[0] + " " + x);

اختر إجابتك لتبدأ الاختبار

كل الأسئلة مع الإجابات (١٢ سؤالاً)

جرّب المقابلة أولاً، ثم افتح الإجابات لتراجعها مع الشرح.
  1. ١. ماذا تطبع شيفرة C# التالية؟ static void Change(int[] arr, int n) { arr[0] = 99; n = 99; arr = new int[] { 0 }; } // inside Main int[] a = { 1, 2 }; int x = 1; Change(a, x); Console.WriteLine(a[0] + " " + x);

    • 0 1
    • 1 1
    • 99 1
    • 99 99

    الشرح: تُمرَّر المعاملات في C# بالقيمة افتراضياً، والقيمة المنسوخة للمصفوفة هي مرجع إلى الكائن نفسه، لذا يظهر تعديل arr[0] خارج الدالة، أما إسناد مصفوفة جديدة إلى arr وتغيير n فيؤثران في النسختين المحليتين فقط. يخطئ كثيرون فيظنون أن المصفوفة تُمرَّر بالمرجع كما مع ref فيتوقعون 0 1.

  2. ٢. ماذا تطبع شيفرة Python التالية، ولماذا؟ total = 0.1 + 0.2 if total == 0.3: print("equal") else: print("not equal")

    • not equal، لأن 0.1 و0.2 لا يُمثَّلان بدقة في النظام الثنائي
    • not equal، لأن == تقارن مرجعي الكائنين لا قيمتيهما
    • equal، لأن Python يقرّب ناتج جمع الأعداد العشرية تلقائياً إلى منزلة واحدة
    • equal، لأن نوع float في Python يخزّن الأعداد العشرية بتمثيل عشري دقيق

    الشرح: ناتج 0.1 + 0.2 في الأعداد العشرية الثنائية (IEEE 754) هو 0.30000000000000004 تقريباً، فلا يساوي 0.3 تماماً، والصواب مقارنة الفرق المطلق بهامش صغير مثل abs(a - b) < 1e-9. يتوقع كثيرون equal لأنهم يحسبون الناتج ذهنياً بالنظام العشري.

  3. ٣. في الشيفرة التالية يرث Square من Rectangle. دالة تستقبل Rectangle وتنفذ setWidth(5) ثم setHeight(4) وتتوقع أن تكون المساحة 20، لكنها تحصل على 16 عند تمرير كائن Square. أي مبدأ من مبادئ SOLID يخالفه هذا التصميم؟ class Rectangle { protected int width, height; void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } int area() { return width * height; } } class Square extends Rectangle { @Override void setWidth(int w) { width = w; height = w; } @Override void setHeight(int h) { width = h; height = h; } }

    • مبدأ فصل الواجهات (Interface Segregation Principle)
    • مبدأ المفتوح/المغلق (Open/Closed Principle)
    • مبدأ المسؤولية الواحدة (Single Responsibility Principle)
    • مبدأ استبدال ليسكوف (Liskov Substitution Principle)

    الشرح: ينص مبدأ ليسكوف على أن كائن الصنف الفرعي يجب أن يحل محل كائن الصنف الأب دون أن يكسر السلوك المتوقع منه، وSquare يغيّر سلوك setWidth وsetHeight فيكسر افتراض الدالة. يختار بعضهم مبدأ المفتوح/المغلق لأن المشكلة ظهرت مع الوراثة، لكن جوهرها أن الصنف الفرعي لا يصلح بديلاً للأب.

  4. ٤. أضفت كائناً من الصنف Point إلى HashSet، ثم بحثت بكائن جديد له الإحداثيات نفسها، فأعاد contains القيمة false. ما سبب المشكلة؟ class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point) o; return x == p.x && y == p.y; } } Set<Point> set = new HashSet<>(); set.add(new Point(1, 2)); System.out.println(set.contains(new Point(1, 2)));

    • أُعيد تعريف equals دون إعادة تعريف hashCode
    • HashSet يقارن العناصر بالمرجع == ولا يستدعي equals
    • معامل equals يجب أن يكون من نوع Point لا Object
    • الحقلان x وy ليسا final فلا تصح مقارنتهما

    الشرح: تحدد HashSet موقع العنصر أولاً بقيمة hashCode ثم تستخدم equals، وبدون إعادة تعريف hashCode يُستخدم التنفيذ الموروث من Object المبني على هوية الكائن، فلا يصل البحث غالباً إلى العنصر المساوي. القاعدة أن الكائنين المتساويين بـ equals يجب أن يكون لهما hashCode واحد، ويغفل عنها كثيرون لأن الشيفرة تُترجم وتعمل دون أي خطأ.

  5. ٥. لديك مصفوفة غير مرتبة من n عدداً صحيحاً، وتريد معرفة هل يوجد فيها عددان مجموعهما يساوي قيمة معينة target. أي طريقة تحقق أفضل تعقيد زمني متوقع؟

    • ترتيب المصفوفة ثم التحرك بمؤشرين من طرفيها نحو المنتصف
    • حلقتان متداخلتان تختبران كل زوج ممكن من العناصر
    • بناء binary search tree من العناصر ثم البحث فيها عن target - x لكل عنصر
    • مرور واحد على المصفوفة مع hash set يُبحث فيها عن target - x

    الشرح: البحث والإضافة في hash set بزمن متوقع O(1)، فيصبح الحل كله O(n)، بينما يحتاج الترتيب أو بناء الشجرة إلى O(n log n) على الأقل، والحلقتان المتداخلتان إلى O(n^2). يختار كثيرون طريقة المؤشرين لأنها أنيقة، لكن الترتيب الذي تحتاجه يجعلها أبطأ.

  6. ٦. تستغرق الدالة التالية وقتاً طويلاً جداً عند استدعاء fib(40). ما السبب الرئيسي، وما الحل الأنسب؟ def fib(n): if n < 2: return n return fib(n - 1) + fib(n - 2)

    • تُحسب القيم نفسها مرات كثيرة، والحل حفظ النتائج بـ memoization
    • استدعاء الدوال بطيء في Python، والحل إعادة كتابة الدالة نفسها بلغة C
    • شرط التوقف n < 2 خاطئ ويسبب استدعاءات لا نهائية، والحل تغييره إلى n == 0
    • عمق العودية كبير جداً، والحل رفع الحد بـ sys.setrecursionlimit

    الشرح: الدالة تحسب fib(n - 2) وغيرها مرات كثيرة في فروع مختلفة، فينمو عدد الاستدعاءات أُسّياً، وحفظ كل نتيجة بعد حسابها أول مرة يجعل التعقيد O(n). يظن بعضهم أن المشكلة في عمق العودية، لكن العمق هنا لا يتجاوز 40 تقريباً.

  7. ٧. كتب زميلك الشيفرة التالية لحذف كل القيم 2 من القائمة. ماذا تطبع؟ nums = [1, 2, 2, 3] for x in nums: if x == 2: nums.remove(x) print(nums)

    • [1, 2, 3]
    • RuntimeError بسبب تعديل القائمة أثناء المرور عليها
    • [1, 2, 2, 3]
    • [1, 3]

    الشرح: عند حذف أول 2 تنزاح العناصر التالية خطوة إلى اليسار بينما ينتقل المرور إلى الفهرس التالي، فيُتخطّى الـ 2 الثاني دون فحص، ولا ترفع Python خطأً مع القوائم في هذه الحالة. يتوقع كثيرون [1, 3]، والحل الصحيح بناء قائمة جديدة مثل [x for x in nums if x != 2].

  8. ٨. تبني نظاماً تصل إليه مهام جديدة باستمرار، ولكل مهمة أولوية، وفي كل مرة يجب سحب المهمة ذات الأولوية الأعلى. ما هيكل البيانات الأنسب؟

    • hash table مفتاحها الأولوية
    • queue عادية بترتيب FIFO
    • مصفوفة مرتبة دائماً حسب الأولوية
    • heap (priority queue)

    الشرح: يتيح heap إضافة مهمة وسحب الأعلى أولوية بزمن O(log n) لكل عملية. يختار بعضهم المصفوفة المرتبة لأن سحب الأعلى منها سريع، لكن إدراج كل مهمة جديدة في موضعها يكلف O(n).

  9. ٩. تعمل على ميزة في فرعك ولديك تعديلات لم تعمل لها commit بعد، وطُلب منك فوراً إصلاح خطأ عاجل في فرع آخر. تريد الاحتفاظ بتعديلاتك دون إضافتها إلى سجل الفرع. ما الأنسب؟

    • حفظها بـ git stash ثم استعادتها بـ git stash pop عند العودة
    • تنفيذ git reset --hard ثم الانتقال، فـ Git يحتفظ بنسخة منها تلقائياً
    • الانتقال مباشرة بـ git checkout، فالتعديلات غير المسجلة تبقى في فرعها الأصلي
    • ضمّها إلى آخر commit بـ git commit --amend ثم الانتقال إلى الفرع الآخر

    الشرح: يحفظ git stash التعديلات غير المسجلة خارج سجل الفروع ويعيد مجلد العمل نظيفاً، ثم يعيدها git stash pop لاحقاً. يظن كثيرون أن هذه التعديلات مرتبطة بالفرع، والحقيقة أنها تنتقل مع git checkout إلى الفرع الآخر أو تمنع الانتقال عند التعارض، أما git reset --hard فيحذفها نهائياً.

  10. ١٠. تكتب unit test لدالة تحسب فاتورة العميل ثم ترسلها عبر خدمة بريد خارجية، فأصبح الاختبار بطيئاً ويفشل أحياناً عند انقطاع الشبكة. ما الحل الأنسب؟

    • زيادة مهلة الانتظار timeout في الاختبار حتى يكتمل الإرسال دائماً
    • إعادة تشغيل الاختبار تلقائياً عند فشله حتى ينجح في إحدى المحاولات
    • استبدال خدمة البريد في الاختبار بـ mock والتحقق من استدعائها
    • حذف الاختبار والاكتفاء بفحص الإرسال يدوياً قبل كل إصدار

    الشرح: الـ unit test يختبر منطق الوحدة معزولاً، واستبدال الاعتمادية الخارجية بـ mock يجعله سريعاً وثابت النتيجة مع التحقق من أن الإرسال طُلب بالبيانات الصحيحة. إعادة التشغيل وزيادة المهلة حلول شائعة، لكنها تخفي عدم الاستقرار ولا تزيله.

  11. ١١. في الجدول employees توجد صفوف قيمة العمود manager_id فيها NULL، لكن الاستعلام التالي لا يعيد أي صف. ما السبب؟ SELECT name FROM employees WHERE manager_id = NULL;

    • العمود manager_id يحتاج إلى index كي يمكن البحث فيه عن قيم NULL
    • يجب كتابة NULL بين علامتي اقتباس هكذا: 'NULL'
    • يجب استخدام <> بدلاً من = عند المقارنة مع NULL
    • المقارنة بـ = NULL لا تعطي TRUE أبداً، والصواب IS NULL

    الشرح: في SQL القياسية تعطي أي مقارنة مع NULL باستخدام = أو <> النتيجة UNKNOWN لا TRUE، فلا يمر أي صف من شرط WHERE، والصواب WHERE manager_id IS NULL. يقع كثيرون في هذا الخطأ لأنهم يعاملون NULL كقيمة عادية، بينما هي تعني قيمة مجهولة.

  12. ١٢. تكتب عملية تحويل مبلغ بين حسابين بأمرَي UPDATE: الأول يخصم من الحساب المرسل والثاني يضيف إلى الحساب المستقبل. كيف تضمن ألا يبقى الخصم منفذاً وحده إذا فشل أمر الإضافة؟

    • تنفيذ أمر الإضافة أولاً ثم أمر الخصم بعده مباشرة
    • تنفيذ الأمرين في transaction واحدة مع ROLLBACK عند الخطأ
    • إضافة قيد CHECK (balance >= 0) على عمود الرصيد في جدول الحسابات
    • إنشاء index على عمود رقم الحساب كي يُنفَّذ الأمران معاً بسرعة أكبر

    الشرح: الـ transaction تجعل الأمرين وحدة واحدة (atomicity)، فإما أن يُثبَّتا معاً بـ COMMIT أو يُلغيا معاً بـ ROLLBACK. تغيير ترتيب الأمرين يبدو حلاً، لكنه ينقل المشكلة فقط، فيبقى المبلغ مضافاً دون خصم إذا فشل الأمر الثاني.

المزيد