العودة إلى المدونة

التحضير لمقابلة Go لمهندس أول: ما يُختبر فعلًا في 2026

مقابلات Go لمستوى مهندس أول تتجاوز الصيغة (syntax) وتختبر حسن التقدير في التزامن، والتعامل مع الأخطاء، والتصميم. يشرح هذا الدليل ما يُسأل عنه وكيف تجيب عنه.

التحضير لمقابلة Go لمهندس أول: ما يُختبر فعلًا في 2026

مقابلة Go لمستوى مهندس أول ليست اختبارًا في الصيغة (syntax). لا يهتم أحد يوظف مهندسًا أول بقدرتك على سرد حلقة for بأجزائها الثلاثة أو بتذكّرك أن append قد يعيد الحجز في الذاكرة. ما يريدون معرفته هو هل تستطيع تتبّع تسريب goroutine، والدفاع عن طريقتك في التعامل مع الأخطاء، وتصميم واجهة API يستطيع الفريق العمل بها لعامين. الأسئلة مفتوحة عن قصد. من يقابلك يراقب طريقة تفكيرك، ولا يتحقق مما إذا كنت قد حفظت المكتبة القياسية.

يغطي هذا الدليل المواضيع التي تفرّق بين مرشح المستوى الأول ومرشح المستوى المتوسط، مع كود قابل للتشغيل لكل موضوع. وهو مبني على ما يقول مطورو Go العاملون إنهم يبنونه ويتعثرون فيه، لا على قوائم «أهم 50 سؤالًا» المنسوخة.

الخلاصة

  • التزامن (concurrency) هو المجال الذي يؤتي فيه التحضير أكبر ثمار. أكثر ما يبنيه مطورو Go هو خدمات API/RPC (75%) وأدوات سطر الأوامر (62%)، وكلاهما يعتمد بشدة على goroutines و channels و context (Go Developer Survey 2024 H2).
  • التحدي الأول المذكور هو كتابة كود اصطلاحي، لا الميزات الناقصة. سمّى 33% من المشاركين «ضمان أن يتبع كود Go لدينا أفضل الممارسات والأنماط الاصطلاحية» أكبر تحدٍ يواجههم، وهي نسبة تفوق من ذكروا أي ميزة لغوية ناقصة (2025 Go Developer Survey).
  • تسابق البيانات (data race) يحدث في بيئة الإنتاج، فتوقع ظهوره في المقابلات. وجد race detector في Uber نحو 2,000 حالة تسابق بيانات في مستودع Go الموحّد (monorepo) لديها، وأصلح 210 مهندسين قرابة 1,100 منها على مدى ستة أشهر (Uber Engineering).
  • أتقن errors.Is و errors.As و %w تمامًا. قال 28% من المشاركين إن Go تفتقد ميزة كانوا يقدّرونها في لغة أخرى، والتعامل مع الأخطاء يتصدر تلك القائمة، لذلك يسبر من يقابلك طريقتك في التعامل معها (2025 Go Developer Survey).
  • generics مطروحة للسؤال، لكنها فخ أيضًا. وصلت في Go 1.18، التي وصفها فريق Go بأنها «أكبر تغيير في اللغة على الإطلاق» (Go Blog). وإشارة المستوى الأول هي معرفة متى لا تستخدمها.
  • استهدف سلسلة الأدوات الحالية. Go 1.26 هي أحدث إصدار مستقر (Go 1.26 release notes). ومن يقابلك يلاحظ حين تصف سلوك إصدار قديم.

جدول المحتويات

ما تختبره فعلًا مقابلة Go لمهندس أول

لا يوجد استبيان موثوق يرتّب «أكثر أسئلة مقابلات Go شيوعًا»، ولا تثق بأي مقال يذكر نسبة مئوية دقيقة. ما يمكنك فعله هو الانطلاق عكسيًا مما يقضي فيه مطورو Go وقتهم. وجد استبيان 2024 أن 75% من المشاركين يبنون خدمات API أو RPC وأن 62% يبنون أدوات سطر أوامر (Go Developer Survey 2024 H2). هذا العمل متزامن ومقيد بالشبكة ومليء باحتمالات الفشل، ولهذا تعود المقابلات دائمًا إلى التزامن و context والتعامل مع الأخطاء.

سأل استبيان 2025 المشاركين عن أكبر التحديات التي تواجههم. ذكر 33% اتباع أفضل الممارسات والأنماط الاصطلاحية. وذكر 28% ميزة يقدّرونها في لغة أخرى ولا توجد في Go، وأكثرها ذكرًا التعامل مع الأخطاء وأنواع المجموع (sum types) وسلامة المؤشرات من nil. وذكر 26% صعوبة إيجاد وحدات (modules) جديرة بالثقة (2025 Go Developer Survey). وجزء كبير من مقابلة المستوى الأول هو تحقق الشركة من أنك استوعبت الأنماط الاصطلاحية التي يجدها ثلث المجتمع صعبة.

المقابلة إذن اختبار لحسن التقدير في معظمها. هل تختار بنية التزامن المناسبة، أم تطلق goroutine بحكم العادة؟ هل تستطيع شرح سبب تغليفك خطأً بدلًا من تسجيله والمضي قدمًا؟ هل تستطيع أن تقول «لن أستخدم generics هنا» وتدعم ذلك بحجة؟

التزامن: جوهر كل مقابلة Go لمهندس أول

التزامن هو الموضع الذي يخسر فيه مرشحو المستوى المتوسط العرض الوظيفي. والخطأ الكلاسيكي هو إطلاق goroutine واحدة لكل وحدة عمل، دون حد أعلى، ودون مسار للأخطاء، ودون طريقة لإيقافها. أما مرشحو المستوى الأول فيبدؤون من نمط محدود السعة.

الـ goroutines رخيصة، وهذا ما يجعل المقاربة الساذجة مغرية. من Go FAQ:

يبلغ المكدس الأولي نحو 2KB على معظم المنصات وتديره بيئة التشغيل، ولهذا يستطيع برنامج واحد تشغيل مئات الآلاف من goroutines في الوقت نفسه. لكن الرخيص ليس مجانيًا. الـ goroutines غير المحدودة تلتهم الذاكرة، وتُغرق الخدمات التي تعتمد عليها، وتخفي التسريبات. والحل المعتاد هو مجمّع العمال.

مجمّعات العمال (worker pools)

يطلق مجمّع العمال عددًا ثابتًا من goroutines ويغذيها بالعمل عبر channel. وهو الجواب المعتاد على «عالج 10,000 رابط دون فتح 10,000 مقبس (socket) في الوقت نفسه».

example.gogo
package main

import (
	"fmt"
	"sync"
)

// fetchStatus stands in for any bounded I/O call (HTTP, DB, RPC).
func fetchStatus(url string) string {
	return "200 " + url
}

func main() {
	urls := []string{"a", "b", "c", "d", "e"}
	const workers = 3

	jobs := make(chan string)
	results := make(chan string)

	var wg sync.WaitGroup
	for range workers {
		wg.Go(func() {
			for url := range jobs {
				results <- fetchStatus(url)
			}
		})
	}

	// Close results once every worker has returned.
	go func() {
		wg.Wait()
		close(results)
	}()

	go func() {
		for _, url := range urls {
			jobs <- url
		}
		close(jobs)
	}()

	for r := range results {
		fmt.Println(r)
	}
}

ينتبه من يقابلك إلى ثلاث تفاصيل. تغلق jobs حتى تنتهي حلقات range عند العمال. وتستخدم sync.WaitGroup لتعرف متى يصبح إغلاق results آمنًا. وتغلق results من goroutine منفصلة حتى تستطيع الحلقة الرئيسية استنفادها. أصِب هذه التفاصيل وتكون قد أظهرت أنك تفهم الصورة كاملة. وهناك تفصيلتان أحدث تدلان على أنك تتابع سلسلة الأدوات. for range workers يتكرر على عدد صحيح، وهذا يعمل منذ Go 1.22. و wg.Go، الذي أضيف في Go 1.25، يحل محل الزوج القديم wg.Add(1) مع defer wg.Done()، وبذلك يزول أشهر أخطاء WaitGroup، وهو نسيان أحد نصفي الزوج.

تسابق البيانات وأداة race detector

توقع سؤالًا من نوع «ما الخطأ في هذا الكود» فيه حالة تسابق. حالات التسابق تظهر في كود الإنتاج باستمرار، لا في المقابلات وحدها. نشرت Uber الأرقام. يحتوي مستودعها الموحّد لـ Go على نحو 50 مليون سطر من الكود موزعة على قرابة 2,100 خدمة Go مستقلة. ووجد race detector لديها ما يقارب 2,000 حالة تسابق بيانات، وأصلح 210 مهندسين قرابة 1,100 منها في ستة أشهر (Uber Engineering).

وحالة التسابق النموذجية هي عدّاد مشترك:

example.gogo
// BROKEN: concurrent writes to count are a data race.
func countBroken(items []int) int {
	count := 0
	var wg sync.WaitGroup
	for _, n := range items {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if n%2 == 0 {
				count++ // unsynchronized write
			}
		}()
	}
	wg.Wait()
	return count
}

الإصلاح الصحيح يتوقف على شكل المشكلة. العدّاد البسيط يحتاج إلى sync/atomic أو sync.Mutex. والتجميع يعمل غالبًا بشكل أفضل مع channel، حيث تُبلّغ كل goroutine عن نتيجتها وتملك goroutine واحدة المجموع. وجواب المستوى الأول يسمّي المقايضة. sync.Mutex أسهل في القراءة، والعمليات الذرية (atomics) أسرع تحت التنافس، والـ channel («شارك الذاكرة عبر التواصل») يتخلص من الحالة المشتركة كليًا. ثم قل «سأشغّله باستخدام go test -race»، لأن هذه العادة تكشف حالات التسابق قبل أن تصل إلى الإنتاج.

Fan-Out و Fan-In

يوزّع fan-out العمل على عدة goroutines، ويدمج fan-in نتائجها في channel واحد. تستخدمه في الإدخال والإخراج المتوازي الذي يغذي مستهلكًا واحدًا، وهو يتكامل طبيعيًا مع مجمّع العمال السابق. يتعثر المرشحون عادة في خطوة الدمج. إغلاق القناة المدمجة يحتاج إلى WaitGroup على المنتجين، مثل القناة results في مجمّع العمال. وإذا استطعت شرح سبب إغلاق النمطين لمخرجاتهما بالطريقة نفسها، فقد أظهرت أنك تفهم ملكية القناة ولا تكتفي بتكرار مقتطف كود.

context: الإلغاء والمهل الزمنية

تمرّر خدمات Go القيمة context.Context عبر مكدس الاستدعاءات كله، وسيطلب منك من يقابلك استخدامها بشكل صحيح. القواعد هي أن context هو المعامل الأول، واسمه ctx، ولا تخزنه أبدًا داخل struct. وهو يحمل الإلغاء والمهل الزمنية والقيم المرتبطة بالطلب عبر حدود واجهات API.

والسؤال التالي الأكثر شيوعًا هو «كيف توقف عاملًا متوقفًا في انتظار استدعاء بطيء؟». تستخدم select على العمل و ctx.Done() معًا:

example.gogo
func process(ctx context.Context, jobs <-chan string) error {
	for {
		select {
		case <-ctx.Done():
			return ctx.Err() // context.Canceled or DeadlineExceeded
		case job, ok := <-jobs:
			if !ok {
				return nil // channel closed, work done
			}
			if err := handle(ctx, job); err != nil {
				return fmt.Errorf("handling %q: %w", job, err)
			}
		}
	}
}

إعادة ctx.Err() تخبر المستدعي لماذا توقفت، فيستطيع التمييز بين انتهاء المهلة والإلغاء المتعمد. الأجوبة الأضعف تتجاهل context أو تتحقق منه في رأس الحلقة فقط. وعندها لن تلاحظ goroutine محتجزة داخل handle الإلغاء أبدًا. ويذكر المرشحون الأقوياء أيضًا أن على الأب استدعاء دالة cancel التي يعيدها context.WithCancel أو context.WithTimeout، عادة عبر defer cancel()، لتحرير الموارد.

كن مستعدًا لسؤال «ما الفرق بين context.WithCancel و context.WithTimeout؟». الأول يلغي عندما تستدعي cancel. والثاني يلغي أيضًا عند انتهاء مهلة زمنية. وعليك استخدام defer cancel() في الحالتين، لأن تسريب goroutine الداخلية الخاصة بـ context هو تسريب للموارد أيضًا.

التعامل مع الأخطاء بأسلوب المهندس الأول

التعامل مع الأخطاء هو الميزة التي يفتقدها مطورو Go من لغات أخرى أكثر من غيرها. في 2025 قال 28% من المشاركين إن Go تفتقد ميزة كانوا يقدّرونها في لغة أخرى، وكان التعامل مع الأخطاء في مقدمة تلك القائمة (2025 Go Developer Survey). وهذا الإحباط هو سبب اختبار المقابلات له. هم يريدون أن يروا أنك تتعامل مع الأخطاء كقيم تحمل معنى، لا كضجيج تسجله ثم تنساه.

والحد الأدنى هو التغليف باستخدام %w حتى يستطيع المستدعون فحص السلسلة:

example.gogo
var ErrNotFound = errors.New("not found")

func loadUser(ctx context.Context, id string) (*User, error) {
	row, err := db.Query(ctx, id)
	if err != nil {
		// Wrap, don't replace. The caller can still see the root cause.
		return nil, fmt.Errorf("loadUser %s: %w", id, err)
	}
	if row == nil {
		return nil, fmt.Errorf("loadUser %s: %w", id, ErrNotFound)
	}
	return row, nil
}

ثم يتحقق errors.Is من وجود قيمة حارسة (sentinel)، ويفك errors.As التغليف للوصول إلى نوع محدد:

example.gogo
user, err := loadUser(ctx, id)
if errors.Is(err, ErrNotFound) {
	http.Error(w, "user not found", http.StatusNotFound)
	return
}

var validationErr *ValidationError
if errors.As(err, &validationErr) {
	http.Error(w, validationErr.Field+" is invalid", http.StatusBadRequest)
	return
}

كن مستعدًا لشرح متى تستخدم كلًا منها. القيمة الحارسة مع errors.Is تناسب الحالة التي يحتاج فيها المستدعي إلى معرفة أي خطأ وقع فقط. والخطأ ذو النوع المحدد مع errors.As يناسب الحالة التي يحتاج فيها المستدعي إلى بيانات منه، مثل اسم حقل أو رمز حالة. وغلّف باستخدام %w فقط عندما يكون الخطأ المغلَّف جزءًا حقيقيًا من عقد واجهة API لديك، لأن التغليف الزائد يفضح تفاصيل التنفيذ. وقل صراحة أيضًا إن panic مخصص لأخطاء المبرمج ولحالات فشل بدء التشغيل التي لا يمكن التعافي منها، لا للحالات المتوقعة مثل سجل غير موجود. واستخدام panic لخطأ عادي طريق سريع لإسقاطك في فرز مرشحي المستوى الأول.

generics: معرفة متى تلجأ إليها

وصلت generics في Go 1.18. وهكذا أعلن عنها فريق Go:

يسأل من يقابلك عن generics ليعرف هل تستطيع كبح نفسك. والجواب الصادق هو أن معظم الكود لا يحتاج إليها، وأن الحزمتين slices و maps في المكتبة القياسية تغطيان الحالات الشائعة أصلًا. استخدم معاملات الأنواع (type parameters) عندما يكون البديل نسخ المنطق نفسه لعدة أنواع، أو اللجوء إلى interface{} وفقدان أمان الأنواع.

والاستخدام الجيد هو دالة مساعدة مقيّدة لا توفرها المكتبة القياسية أصلًا:

example.gogo
import "cmp"

// Clamp constrains v to the range [lo, hi] for any ordered type.
func Clamp[T cmp.Ordered](v, lo, hi T) T {
	if v < lo {
		return lo
	}
	if v > hi {
		return hi
	}
	return v
}

يقصر القيد cmp.Ordered من الحزمة القياسية cmp النوع T على الأنواع التي تدعم < و >. واختيارك للمثال يقول شيئًا عنك أيضًا. لا تكتب دالة Max عامة. فلدى Go الدالتان المدمجتان max و min منذ 1.21، و slices.Max و slices.Min تتعاملان مع الـ slices، لذلك فإن كتابة نسخة بيدك توحي بأنك تلجأ إلى generics بحكم العادة. أما Clamp فتبرر معامل النوع لأن لا شيء في المكتبة القياسية يؤدي العمل نفسه. قبل generics كانت دالة كهذه تحتاج إما إلى نسخة لكل نوع، وإما إلى interface{} مع تأكيدات أنواع (type assertions) قد تسبب panic وقت التشغيل. أما الآن فالمترجم يتحقق منها. لكن إذا كانت الدالة لا تتعامل إلا مع int، فاكتبها لـ int. التعميم المبكر رائحة كود في Go كما في أي مكان آخر، ومن يقابلك يأمل أن تقول ذلك.

interfaces وتصميم واجهات API

أسئلة interfaces تختبر الحس التصميمي، وهو جزء كبير من معنى كلمة «أول». والقول المأثور في Go الذي يجب أن تعرفه هنا هو «اقبل interfaces، وأعد structs». على الدالة أن تقبل أضيق interface تستخدمه فعلًا، وأن تعيد نوعًا محددًا، حتى يبقى المستدعون أحرارًا في خياراتهم.

example.gogo
// Good: accepts the minimal behavior it needs.
func Copy(dst io.Writer, src io.Reader) (int64, error) {
	return io.Copy(dst, src)
}

لا تهتم Copy بما إذا كان src ملفًا أو اتصال شبكة أو مخزنًا مؤقتًا. ولأنها تقبل io.Reader فهي تعمل معها كلها. ولهذا فإن io.Reader و io.Writer، ولكل منهما دالة واحدة، هما أكثر interfaces إعادة استخدام في اللغة.

وهناك نقاط أخرى تستحق الذكر. أبقِ interfaces صغيرة، ويُفضّل أن تحتوي على دالة أو دالتين. وعرّفها في الحزمة التي تستهلكها لا في الحزمة التي تنفذها، حتى لا تعتمد الحزم على بعضها بلا سبب. وتخلّص من العادة الشائعة في لغات أخرى، وهي وضع interface أمام كل شيء. الـ struct الذي له تنفيذ واحد لا يحتاج إلى interface بعد. أضف واحدًا عندما يحتاج إليه فعلًا تنفيذ ثانٍ أو بديل اختباري (test double). ومن يقابلك يلاحظ التجريدات المبنية على التوقع، وهذا هو نوع الكود الذي يتعامل معه الـ 33% الذين يعانون مع الأنماط الاصطلاحية (2025 Go Developer Survey).

جلسة الكتابة المباشرة: بناء rate limiter

تطلب الجلسة المباشرة عادة شيئًا صغيرًا لكنه متزامن. ومحدد المعدل (rate limiter) يتكرر كثيرًا لأنه يجمع goroutines و channels والوقت والتنظيف بعد الانتهاء. ومحدد يعتمد على دلو الرموز (token bucket) مبني على time.Ticker قصير بما يكفي لكتابته في الغرفة، ويمنحك الكثير لتتحدث عنه:

example.gogo
type Limiter struct {
	tokens chan struct{}
	stop   chan struct{}
}

func NewLimiter(perSecond int) *Limiter {
	l := &Limiter{
		tokens: make(chan struct{}, perSecond), // burst capacity
		stop:   make(chan struct{}),
	}
	ticker := time.NewTicker(time.Second / time.Duration(perSecond))
	go func() {
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				select {
				case l.tokens <- struct{}{}: // refill one token
				default: // bucket full, drop the tick
				}
			case <-l.stop:
				return
			}
		}
	}()
	return l
}

// Allow reports whether a request may proceed right now.
func (l *Limiter) Allow() bool {
	select {
	case <-l.tokens:
		return true
	default:
		return false
	}
}

func (l *Limiter) Close() { close(l.stop) }

الكود أقل أهمية من طريقة حديثك عنه. أشر إلى أن القناة tokens ذات المخزن المؤقت تعطيك سعة اندفاع (burst) بلا تكلفة. واشرح أن select الداخلي مع default يهمل نبضات إعادة التعبئة حين يكون الدلو ممتلئًا بدلًا من أن يتوقف انتظارًا. واذكر أن Close يوقف goroutine الخلفية حتى لا تتسرب، وأن ticker.Stop() يحرر المؤقت. ثم تحدث عن البديل. في بيئة الإنتاج ستستخدم عادة golang.org/x/time/rate بدلًا من كتابة هذا بنفسك، ومعرفة متى تختار الحل القياسي بدلًا من حل مخصص جزء من حسن التقدير الذي يوظفون من أجله.

الأسئلة الشائعة

ما أكثر المواضيع تكرارًا في مقابلة Go لمهندس أول؟

التزامن و context والتعامل مع الأخطاء و interfaces و generics، بهذا الترتيب تقريبًا. وهذا الترتيب يتبع ما يبنيه مطورو Go. 75% يكتبون خدمات API/RPC و 62% يكتبون أدوات CLI (Go Developer Survey 2024 H2)، وكلا النوعين من العمل متزامن ومليء باحتمالات الفشل. ولا توجد إحصائية منشورة موثوقة ترتّب عدد مرات ظهور أسئلة بعينها، لذلك تعامل بشك مع أي مقال يدّعي وجودها.

هل أحتاج إلى معرفة generics في مقابلة Go؟

عليك أن تفهمها، والأهم من ذلك أن تعرف متى لا تستخدمها. وصلت generics في Go 1.18 بوصفها أكبر تغيير في تاريخ اللغة (Go Blog)، فهي مطروحة للسؤال. ومع ذلك ما زال معظم كود Go الاصطلاحي يتجنبها، ويحصد مرشحو المستوى الأول نقاطًا عندما يشرحون أن الحزمتين القياسيتين slices و maps تغطيان معظم الاحتياجات.

ما مدى أهمية race detector؟

مهمة بما يكفي ليتوقع منك من يقابلك أن تذكرها دون أن يسألك عنها. وجد race detector في Uber ما يقارب 2,000 حالة تسابق بيانات في مستودع Go الموحّد لديها، وأصلح 210 مهندسين قرابة 1,100 منها على مدى ستة أشهر (Uber Engineering). وقول «سأشغّل go test -race» يخبر من يقابلك أنك عملت على كود إنتاج.

أي إصدار من Go يجب أن أستهدف؟

سلسلة الأدوات المستقرة الحالية، وهي Go 1.26 في بداية 2026 (Go 1.26 release notes). ووصف سلوك إصدارات قديمة يكشف قِدم معرفتك، مثل الادعاء بأنك تحتاج إلى حلقة بثلاثة أجزاء للتكرار على عدد صحيح. فالشكل for range n يعمل منذ Go 1.22.

هل ما زال التخصص في Go يستحق العناء؟

نعم. أفاد 17.4% من المطورين المحترفين بأنهم عملوا بشكل موسّع مع Go خلال العام الماضي (Stack Overflow Developer Survey 2025). وفي الاستبيان الرسمي قال 91% من المشاركين إنهم راضون عن اللغة، وقال 63% إنهم راضون جدًا (2025 Go Developer Survey). والطلب على مهندسي Go من المستوى الأول يتبع هذا الانتشار.

ابدأ مسارك نحو مستوى المهندس الأول في Go

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

LevelUpGo مبني لهذا الغرض. كل درس تمرين في المتصفح يعمل على أحدث إصدار مستقر من Go ويقيّم كودك تلقائيًا، فتتدرب بالطريقة التي تختبرك بها المقابلة بدلًا من الاكتفاء بالقراءة.

إليك أين تتدرب على كل موضوع من هذا المقال:

  • التزامن و context و channels: تبني دورة Go Fundamentals مفاهيم goroutines و channels والإلغاء خطوة بخطوة من المبادئ الأولى. والبدء فيها مجاني ولا يحتاج إلى بطاقة ائتمان.
  • التعامل مع الأخطاء و interfaces والأنماط الاصطلاحية: يضم مسار Clean Go Code تمارين مقيّمة على errors.Is/errors.As و interfaces الصغيرة والحس التصميمي الذي يغطيه هذا المقال.
  • تُظهر خارطة طريق LevelUpGo الكاملة كيف تتدرج الدورات من الأساسيات إلى التصميم على مستوى المهندس الأول.

ابدأ الدورة المجانية، واكتب Go كل يوم حتى موعد المقابلة، وستدخل قادرًا على شرح طريقة تفكيرك بدلًا من التخمين فيها.

اكتب Go كما يكتبها مهندس أول

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

جرّب درسًا مجانيًاأو أنشئ حسابًا مجانيًا