Interview notes

 

ALWAYS ASK WHAT THE FOCUS OF AN INTERVIEW IS

ALWAYS TALK SLOWLY AND CLEARLY

BE YOURSELF AND THEY'LL LOVE YOU


Description of current role

Daily routine:

Three Amigos

Backlog refinement

Sprint planning

Review

Retrospective


Work within Agile process.  



Asynchronous programming

Implemented using the async and await keywords. Used for I/O bound operations, such as file access, database queries, and network communication.


Prevents main-thread blocking while performing synchronous I/O operations.


IDesposible 

Used for unmanaged resources via the dispose method or using statement



Inversion of Control

Control of flow is inverted to an external framework or container rather than the program itself.

Promotes loose coupling.

Improve the modularity.

Delegates to an external container which manages the lifecycle and dependencies. 


Dependency injection

Implementation of Inversion of Control, 

Class no longer initializes its dependencies but instead accepts them through constructors or properties.


Three commonly used service lifetimes are Singleton, Scoped, and Transient.


Singleton: Created once and lasts the application's runtime.

Scoped: Created for each scope, usually each request 

Transient: Created every time the dependency is called.


Simplifies unit testing by replacing actual dependencies with mockups.


Enhances modularity and flexibility.


Unit testing


Unit testing is used to ensure application logic runs according to specifications. 

Testing frameworks: NUnit or xUnit. 

Mocking libraries: Moq


Code coverage: ideally around 80%. 

Check code coverage: SonarCube or JetBrains dotCover 


Locking mechanisms

Avoid race conditions and deadlocks in multithreading code. Locking mechanisms can be implemented using. lock, Monitor, Mutex, and Semaphore.

    L   Locks: Ensure only one thread.

·        Mutex (Mutual exclusion): Similar to a lock.  Ensures that only one thread can access a protected resource at a time.  mut.WaitOne();
A mutex (Mutual exclusion) is the same as a lock but it can be system wide (shared by multiple processes). It is used to synchronise access to a resource.

·        Semaphores: Limited number of threads.
_pool = new Semaphore(initialCount: 0, maximumCount: 3);

        Monitors include mutual exclusion (just like locks) but also offer waiting, signaling, and notification functionality.

        Monitors can put threads in a wait state and signal specific threads to resume execution.

        Example in C#: Using Monitor.Enter and Monitor.Exit, with optional Monitor.Wait and Monitor.Pulse.

Monitors are best for managing synchronization within a single process and offer additional inter-thread signaling capabilities, while Mutexes are suitable for scenarios where multiple processes need to coordinate access to shared resources, albeit with a slightly higher overhead.


A lambda function is a concise, anonymous function defined without a name, often used for short, simple operations. Lambda functions are typically defined inline and are useful when a function is needed temporarily or as an argument to higher-order functions.





LINQ

Allows writing queries to data sources in a declarative and consistent way, used to perform queries to memory (lists or arrays), databases, and XML  Database via an ORM, such as Entity Framework.


Troubleshooting

Check server memory and CPU.  Check recently added code.  Check exceptions in logs.  Is it just for one or all users?  Is there a data leak?



What you know about Cubic – would be great if you could do some research on who we are, what we do, some of our products etc

They specialize in providing solutions for connected cars, Internet of Things (IoT) devices, and other connected platforms.

Global Connectivity Management Platform: Cubic Telecom offers a platform that allows companies to manage and optimize connectivity for their devices and vehicles globally. This includes services such as SIM card management, data analytics, and real-time monitoring.


Connected Car Solutions: Cubic Telecom provides connectivity solutions for the automotive industry, enabling connected car services such as in-car entertainment, navigation, and vehicle telematics.


Internet of Things (IoT) Connectivity: Their platform supports IoT devices, helping companies connect and manage large numbers of devices across various industries, including healthcare, agriculture, and smart cities.


Embedded SIM (eSIM) Technology: Cubic Telecom has been involved in providing embedded SIM solutions, allowing devices to have programmable SIMs that can be remotely provisioned and managed.


Mobile Data Plans: Cubic Telecom has offered mobile data plans for various connected devices, allowing users to access the internet and data services globally.


Why do you want to work for Cubic

Attracted to the tech stack and then successful company.  Different sort of work from the average fintech or gaming or educational.  Good Glassdoor score.


What can you bring to the role

Bring over 8 years experience of working with C# within TDD to industry standards, Security Champion. 

Leadership and management skills.

Coaching and mentoring skills.  

Bring over 20 years experience of developing software solutions.

Bring an enthusiasm to learning new skills and new technology. 

Set up a team before, understand what is required to mentor and coach 



Agile experience / Scrum Knowledge

Planning - story points - 1,2,3,5,8,13 - get sprint capacity

Acceptance criteria: given -when -then

Verifiable outcomes: definition of done

Expectations, user stories

Scope

Priorities/critical

Edge cases: if it can go wrong, it will go wrong.


Review - showing off what you've done

Retrospective - what went well


Process for communication, process for conflict



C# .Net experience

Coaching/Mentoring

I have a coached and mentored. Difference between coaching and mentoring.

Mentoring:

  1. Establish Clear Expectations: Clearly communicate the goals and expectations of the mentoring relationship. Help your junior colleague understand what they can expect from you as a mentor and what you expect from them in terms of commitment, effort, and goals.
  2. Build Trust and Rapport: Create a supportive and trusting relationship with your mentee by being approachable, empathetic, and respectful. Show genuine interest in their growth and development, and be open to their thoughts, concerns, and ideas.
  3. Provide Constructive Feedback: Offer feedback regularly and constructively, focusing on both strengths and areas for improvement. Provide specific examples and actionable suggestions to help your mentee learn and grow. Encourage them to reflect on their experiences and take ownership of their development.
  4. Encourage Continuous Learning: Foster a culture of continuous learning and development by encouraging your mentee to seek out new opportunities for skill enhancement, knowledge acquisition, and professional growth. Share resources, recommend relevant workshops or courses, and support their efforts to expand their expertise.
  5. Offer Guidance and Advice: Provide guidance and advice based on your own experiences and insights. Share lessons learned, best practices, and strategies for overcoming challenges. Encourage your mentee to explore different approaches, think critically, and make informed decisions.
  6. Set Realistic Goals: Help your mentee set realistic and achievable goals that align with their interests, skills, and career aspirations. Break larger goals into smaller, manageable tasks, and track progress over time. Celebrate milestones and successes along the way to maintain motivation and momentum.
  7. Promote Networking and Relationship Building: Encourage your mentee to build professional networks and establish meaningful relationships within their industry or field of interest. Offer introductions, facilitate networking opportunities, and share tips for effective networking and relationship building.
  8. Lead by Example: Serve as a role model for professionalism, integrity, and ethical conduct. Demonstrate a strong work ethic, effective communication skills, and a commitment to continuous improvement. Lead by example in how you approach challenges, collaborate with others, and strive for excellence in your own work.
  9. Be Patient and Supportive: Recognize that learning and development take time, and be patient and supportive as your mentee navigates their professional journey. Offer encouragement, reassurance, and guidance during times of difficulty or uncertainty. Foster a positive and nurturing environment where your mentee feels empowered to learn and grow.
  10. Seek Feedback and Adapt: Solicit feedback from your mentee about the mentoring process, and be open to adapting your approach based on their needs and preferences. Continuously assess and refine your mentoring strategies to ensure they remain effective and meaningful.


Microservices experience

AutoEntry started as a monolithic app and has broken some services out into microservices.

Processing PDF project using durable functions initiated by a service bus on Azure.


Describe a feature you are proud of


GDPR project:
Deleting non-active users and their data to fulfil GDPR requirements.  Project that could not have bugs.  It needed to be robust, simple and reliable.  Used event sourcing to build an efficient and auditable system.  Used even


Found and fixed bug where invoices were being processed from forwarded emails of cancelled subscription accounts.  This cost the AutoEntry thousands.


Adding parallel for each to improve credits syncing which was undermining processing times.  Much simpler and quicker solution than one proposed by Cosman which was to remove workflows from the system.

·        Sage For Accountants Billing – Document counting

·        IDP – OAuth2 Authentication – BFF pattern

·        Fixed price subscriptions

·        GDPR project

·        QuickSights App Direct billing

·        Go Cardless billing

·        Subscriptions, integration with Stripe, multiple types of subscriptions, creating and sending invoices.

·        Owned, brought developers into the wider project

·        Integrate with many accounting software solutions

IBAN number paste

Post code verifier 


Discuss a failure

Ability to end all other active sessions by removing the session from Redis cache.  My belief was that a next click sign out would be sufficient, but the product owner wanted it to happen immediately. Which meant polling and endpoint.  We have no web sockets or signal r set up on AutoEntry.  This took the site offline when released into the production environment.  


Benefit of going from monolith to microservices

Scale up services that are being heavily used without scaling up the entire app.

Scale down services that are not being used.

Reuse services across different products.

Faster deployment.  Can develop, test and deploy features independently. 

Fault isolation.  One failing microservice won't necessarily effect the entire app.



When not to go from monolith to micro-services

When it creates greater overhead.  Important not to break into micro services just for the sake of it.

Small scale and simple app would not be the best candidate to be divided into micro services.

MS require additional infrastructure and overhead.  

Well established monolith that is well maintained and meets current and future requirements. 







How do you ensure your application is scalable, extensible, and secure


Scalability:

  • Horizontal Scaling: Design your application so that it can scale horizontally by adding more instances. Distribute the workload across multiple servers to handle increased demand.
  • Load Balancing: Use load balancers to distribute incoming traffic evenly across multiple servers, ensuring optimal performance.
  • Caching: Implement caching strategies to reduce the load on databases and improve response times.

Extensibility:

  • Modular Design: Divide your application into modules with clear interfaces. This allows you to extend or replace modules without affecting the entire system.
  • Dependency Injection: Use dependency injection to inject dependencies into components, making it easier to replace or extend functionalities.
  • APIs and Microservices: Expose APIs for external integrations and consider a microservices architecture for independently deployable and scalable services.

Security:

  • Follow OWASP Top 10.  Run Bug bounty and DAST and SAST scans.
  • Authentication and Authorization: Implement secure authentication mechanisms, such as OAuth2 or JWT, and ensure proper authorization to control access to resources.
  • Data Encryption: Encrypt sensitive data both in transit (using protocols like HTTPS) and at rest (using encryption algorithms).
  • Regular Security Audits: Conduct regular security audits and vulnerability assessments to identify and address potential security issues.
  • Input Validation: Validate and sanitize user inputs to prevent common security vulnerabilities like SQL injection and cross-site scripting (XSS).

Performance:

  • Optimized Database Queries: Ensure that database queries are optimized, and use indexing where necessary to improve query performance.
  • Asynchronous Processing: Implement asynchronous processing for non-blocking operations, improving overall system responsiveness.
  • Content Delivery Networks (CDNs): Use CDNs to distribute static assets and reduce latency for users across the globe.


Monitoring and Logging:

  • Logging: Implement robust logging to capture errors, warnings, and relevant information for debugging and auditing purposes.
  • Monitoring Tools: Use monitoring tools to track application performance, resource usage, and potential issues. Implement alerts for critical events.
  • Documentation:
    • Code Documentation: Maintain clear and up-to-date documentation for your codebase to assist developers in understanding and extending the application.
    • API Documentation: Document APIs thoroughly to facilitate external integrations and collaborations.

Event sourcing

persistence pattern, ensuring that every data change is recorded as an immutable event. These events serve as the single source of truth for the system's state. 


Event-driven architecture, these events are passed on to various parts of the system in a loosely coupled manner.

 ideal for microservices-based systems


CQRS

Command Query Responsibility Separation.  Separates the responsibilities of handling commands (writes) and queries (reads).  You don't have to make compromises 

CQRS also means the domain logic does not become welded to the data layer and this provides flexibility and the chance  

The Command-Query Separation principle states that the method should be either a command or a query, but not both.

Violation of the CQS principle can degrade the code readability and testability.

If the command must return a value, consider split it into two separate methods: command and query.


Advantage of CQRS

Improved scalability 

Better performance optimisation for read and write operations

Flexibility in choosing storage mechanisms 

Clearer separation of concerns



DDD Understanding

Under domain-driven design, the structure and language of software code (class names, class methods, class variables) should match the business domain.


Difference between encryption and hashing

Encryption can be unencrypted with the encryption key while hashing is one way that is not meant to be reversed.  SHA-256 or SHA-512 or the standard.


Understanding of event driven design

The code will have sources and listeners that get triggered when different events are raised.  Events are a signal that a certain action has occurred. These components are decoupled for flexibility and extensibility.  In a microservice environment the events are distributed using an Event Bus.  Handlers and callbacks are methods that execute in the response of a specific event.



Functions as a Service (FaaS) offers several advantages and disadvantages:


Advantages:

  1. Scalability: FaaS platforms automatically scale functions in response to incoming requests or events, allowing applications to handle varying workloads without manual intervention. This enables cost-efficient use of resources and ensures optimal performance during peak loads.
  2. Cost Efficiency: FaaS typically follows a pay-per-use pricing model, where you only pay for the resources consumed during function execution. Since functions are short-lived and only run when triggered, you can save costs compared to traditional server-based architectures where resources may be underutilized.
  3. Reduced Operational Overhead: FaaS abstracts away infrastructure management, including server provisioning, scaling, and maintenance. Developers can focus solely on writing code and defining triggers, without needing to worry about underlying infrastructure concerns.
  4. Fast Development and Deployment: FaaS promotes rapid development and deployment cycles by allowing developers to focus on writing small, focused functions. This can lead to faster time-to-market for new features and updates.
  5. Event-Driven Architecture: FaaS encourages an event-driven architecture, where functions respond to specific events or triggers. This makes it well-suited for building event-driven and microservices-based applications, enabling greater flexibility and modularity.


Disadvantages:


  1. Cold Start Latency: FaaS platforms may experience latency during "cold starts," where the platform needs to initialize a new execution environment for a function that hasn't been used recently. This can introduce delays in response times for the first invocation of a function.
  2. Execution Time Limits: FaaS platforms typically impose limits on the maximum execution duration for functions. Long-running tasks may need to be broken down into smaller, shorter functions or handled using alternative approaches.
  3. Vendor Lock-In: Adopting a specific FaaS platform may lead to vendor lock-in, as each provider offers its own proprietary APIs and services. Migrating functions between different FaaS providers or to an on-premises environment may require significant effort and may not be straightforward.
  4. Limited State Management: FaaS functions are typically stateless, meaning they do not retain state between invocations. While this simplifies scaling and deployment, it may require additional effort to manage stateful operations or share data between function invocations.
  5. Debugging and Monitoring Challenges: Debugging and monitoring functions in a FaaS environment can be more challenging compared to traditional server-based architectures. Tools and techniques for debugging and monitoring may be limited, requiring additional effort to ensure visibility into function execution and performance.



Ownership of design and implementation for multiple integration solutions

·        Sage For Accountants Billing – Document counting

·        IDP – OAuth2 Authentication – BFF pattern

·        Fixed price subscriptions

·        GDPR project

        Daily job

        1) Queue users into delete log: update active users and add in new inaction users

        2) Send warning emails and update status

        3) Disable users and update status

        4) Delete users and update status

        Generate reports

·        QuickSights App Direct billing

·        Go Cardless billing

·        Subscriptions, integration with Stripe, multiple types of subscriptions, creating and sending invoices.

·        Owned, brought developers into the wider project

·        Integrate with many accounting software solutions

 

Building and leading a team of engineers and providing governance and oversight of their deliveries

·        Contracted in UK.  Very quick and adaptable at learning new technologies.  HMH 35 games, created proof of concept in CreateJS to replace Flash games.

 

·        Leading teams in AutoEntry and HMH

o   Involved in set up of team in AutoEntry

o   Interviewed staff

o   Performed one-on-ones: One on ones should have two streams: performance measurement and career progression.

o   Performance measurement

o   A lot of performance measurement took place through weekly one-on-one’s.  Took notes on development and performance and gave feedback of work through the week.  Meant that team members had a good idea where they were come end of year assessment already.

o   Team cohesion:

§  Helps that team members understand each other perspective

§  Had issues with clash of personalities

§  Important to not allow political discussion

§  DISC (Dominance, Influence, Steadiness, Compliance) profiling: Peacock, Panther, Dolphin, Owl

·        Panther: motivator, leader, risk-taker, focused, big thinker, outspoken and adventurous.

·        Peacock: motivator, entertainer, colourful, exciting, quick-witted, fun and adventurous.

·        Dolphin: peace-maker, giver, follower, insightful, listener, slow to change and introspective.

·        Owl: thorough, detail-oriented, sequential, analytical, methodical and orderly.

§  Belbin team members

·        Build a culture of how we react to different situations:

o   We react calmly to critical problems and take the issue step by step.

o   We have a collaborative,  no blame culture.  Everyone will make a mistake at some point.  There should no sanctimony within a team.

o   A culture of recognition and sharing praise, this needs to come from the leader.

o   Non competitive and collaborative.

·        Studied leadership, like to employ a Servant Leadership style

o   Listen more than they speak

o   Respectful, emphatic and have a genuine interest in everyone

o   Predictable and clear => approachable and trustworthy

o   Assist and empower their team to grow and be well

o   Prioritizes  the needs of the team

o   Commitment to personal growth

·        Ken Blanchard three aspects of Servant Leader

o   Clear goals

o   One minute praising

o   One minute redirect

·        Team direction

o   Purpose: provide convenience

o   Picture of the future: always be reliable

§  Values: SEEU Security Efficiency  Effectiveness Usability

·        From this vision, the team can set goals with direction and meaning.

·        Who’s got the monkey?

 

Manage relationships with external partners and vendors

·        In HMH, managed relationship with stakeholders in the US and development team in India.

·        Important to have reputation of being trustworthy

·        Managed relationships with internal stakeholders, different integrations within Sage.

·        Important to:

o   Clearly define expectations of a project to avoid misunderstandings

o   Clearly define requirements

o   Define detailed scope of a project

o   Define process of communication and regular updates

o   When dealing with external vendors we need contractual agreements

o   Define process for managing conflict resolution

o   Collaborate and build relationship

o   Identify risks and develop contingency plans

 

Deliver robust technical solutions that form part of the core product

·        If it can go wrong, it will go wrong

·        First and most important rule when you work in a kitchen is clean as you go.  This is mutually beneficial for all workers in sharing a safe, efficient and pleasant working environment.   Similarly, a codebase is a shared working environment.  It’s imperative this is followed in software team.

·        In Sage, projects form part of the core product

o   Follow high standards of coding

o   SOLID

What is the benefit of SOLID
Provide valuable guidelines for designing maintainable, readable and scalable software.


When is it ok to not use SOLID

It's important to remember there are just guidelines.  

Simple and small projects: might create greater overhead and complexity to implement all principles

Open - Closed principle: when there's a bug, then modification is ok.

Legacy code: introducing SOLID principles might cause more problems.

§  Single responsibility principle

·        Each class should have a single responsibility

·        Strive for high cohesion and loose coupling

·        Keep classes small, focused and testable.

§  Open/Closed principle

·        Open for extension, closed for modification

§  Liskov Substitution Principle

The parent class must be able to be replaced by any derived class without causing errors in the program.

·        Subtypes must be substitutable for their base types

·        Square is not substitutable for a rectangle

§  Interface Segregation Principle

                    Each class should only implement the interfaces that will actually be used.

·        Clients should not be forced to depend on methods they do not use

·        Prefer small, cohesive interfaces to large, expansive ones.

§  Dependency Inversion Principle

Example: Using interfaces in Controllers and using DI to fill with either a mock or repository (Database implementation).

·        High level modules should not depend on low level modules. 

·        Both should depend on abstractions.

·        Abstractions should not depend on details. 

·        Details should depend on abstractions.

·        Interfaces and mocks


Gang of Four design patterns

 

DAST: Dynamic Application Security Testing

Evaluating a running application from the outside, mimicking a real world attack.  Set up Netsparker.

 

SAST: Static Application Security Testing

Analysis on source code without executing the program. 

Set up the Fortify scans with Team city, run daily. Fine tune to discard some warnings.

 

Bug bounty

Set up engagement rules in Hacker One.  Created bugs from resulting reports.

 

File Path Traversal Attack: change the folder name for an upload. BurpSuite man in the middle or Chrome Dev tools.  Never trust any data from the user.  ../../etc/password

Encoded Path Traversal attack: %2E%2E2F% = ../   or 2F% = /

Null byte injection path traversal path: ../../etc/passwd%00.png – checking is png, null byte will terminate the sequence for a download, so actual file that gets downloaded is etc/passwd

 

Never let the users file input drive file I/O.  Use an allow list.

The most effective way to prevent file path traversal vulnerabilities is to avoid passing user-supplied input to filesystem APIs altogether.

Run applications with minimal necessary permissions.  Limits scope of damage.

Restrict file system access to only needed directories and files.

File system access control should be very strict.  Explicitly define what files and directories an application can read and write to.

 

AVS5.0 File I/O Requirements

Don’t use user submitted file name.

 

Uploads

Disk Flooding attacks: Zip bombs: 42k zip file that unzipped to 4.5 petabytes.

Injection: metadata, file paths, command injection

Malware distribution: verifying image headers not enough. 

Only allow authorised users can upload.

Per user, per file quota – write to individual user folder.

File name extension, MIME type detection and file header check – light check

Headless antivirus: S3 Sophos and Clam AV, use all anti virus products.

Use Image rewriting.  Use ImageMagick to change file by one pixel and save back to disk.

Drop Lambda if anything goes wrong.

Unzip strategies should have OS level configuration where each zip file is unzipped within its own folder with only those permissions inside of that.  Avoid accepting zip files.

Log failures and lock accounts.

Don’t depend on CloudFlare or WAFF to save you.  Build software that is immune to the problems. Firewalls can be bypassed.

 

SQL Injection

Never use string building to build a SQL query or you can SQL injection as in 1 = 1.  Universal truth, flattens the where clause. Add cmd shell to SQL injection. Xp_cmdshell

Applications that insert untrusted data into a database queries via string building allows attackers to execute arbitrary queries against backend databases.

Look at: Payloads All the Things.  SQL Map can pull a whole database one SQL at a time.

Use parameterised query to build SQL queries, even for static variables.  ORM Lite or EntityFramework.

 

Log injection

GDPR issues – don’t log PII or ensure that you encrypt PII

 

 

Security Logging

Event logging and event monitoring.  Security Logging and monitoring.  Be aware of GDPR.

 

 

§  Code reviews:

·        Security

o   Follow Owasp top 10 security

§  Broken object level authorisation

§  Broken authentication, function level, object level

§  Unrestricted – no rate limiting

§  Service side request forgery

§  SQL and other Injection

§  Security misconfiguration

§  IDOR Insecure direct object references

·        Efficient queries/code

·        Effectiveness

·        Readability:

o   Use abstraction

o    Correctly named variables

o    Use custom exceptions, if no generic exception

o    Duplicated code

o    Long methods

o    Large classes

o    Long parameter list

o    Dead code

o    Feature envy

o    Data clumps

o    Primitive obsession

o    Code laid out in self-explanatory, conspicuous. Comments only when necessary

·        Unit tests

 

o    Monitoring using vital statistics and health checks

o     

 

Collaborate across multiple engineering teams and work across a diverse technology stack

·        As a contractor for three years, I had to be adaptable to the tech stack that I was working with.

·        Used side project to build and learn React

·        Already work across several teams.

 

 

 

 

 

 

 

 

Technical interview:

Sealed class

It prevents a class from being inherited and used as a base class


Dependency injection

A design pattern where dependencies are injected into a class, improving modularity and testability. 


Dynamic v var

You might choose dynamic over var when dealing with scenarios where the type of a variable is not known until runtime.  dynamic allows you to defer the type checking until runtime.

var cache = new Dictionary<string, dynamic>();


volatile keyword

ensures that a variable is read and written directly from and to the memory preventing certain compiler optimisations.

The field may be changed by multiple threads and certain optimisations should be avoided to ensure correct behaviour in a multithreaded environment.

Is

Is is used to check if object is a certain type. 

As

Safe casting, cast an object as - returns null if not castable.

Lock

Lock is used for mutual exclusion, to ensure something is threadsafe.  Only allows one thread at a time.


== vs .Equals()

== is used for reference or value equality while .Equals can be overridden for custom types to provide equality.


params

allows a method to accept a variable number of parameters


throw v throw ex

throw preserves the original call stack while throw ex resets the call stack potentially losing valuable debugging information




Value types v Reference types:

Value types

Variables that store data. Value types are stored on stack.
They contain the actual values. Example: int, enum, structs. 

Reference types

Variables that store reference to actual data.

Reference types stored on heap but contain the address on heap.
example: class, interface, delegate, string, object, Array

Value Type

Reference Type

They are stored on stack

They are stored on heap

Contains actual value

Contains reference to a value

Cannot contain null values. However this can be achieved by nullable types

Can contain null values.

Value type is popped on its own from stack when they go out of scope.

Required garbage collector to free memory.

Memory is allocated at compile time

 Memory is allocated at run time

Stack v Heap

The stack is generally used for storing small, short-lived variables such as local variables and function parameters, while the heap is generally used for storing larger, longer-lived objects.

Stack

·        Value types

·        Local variables and function call information

·        Last in, first out

·        Very fast to allocate and deallocate

·        Limited size. If the stack grows too large, it can cause a stack overflow, which can lead to a crash or other unpredictable behaviour.

·        Used for value types

·        Lifetime of a variable on the stack is limited to the function in which it was created

 

Heap

·        Reference types

·        Used to store objects

·        Called heap because it is not organised and accessed randomly.

·        Objects allocated using the new keyword.

·        More flexible than stack, but slower to allocate and deallocate.

·        No fixed size and garbage collector must monitor.

·        Not tied to any particular function and can outlive the function in which it was created

Boxing and unboxing

Boxing-- Converting a value type to a reference type

·        Memory is allocated on heap

·        Value is copied from stack to heap

·        Reference is updated to point on heap

·        Unboxing- Converting reference type to value type.

 

Struct v Class

Struct

·        Struct is like a class that is used to store data

·        Value type, stored on the stack, so faster to access

·        When you assign to another variable, a copy is made

·        Value based equality comparison, changing one does not change others

·        Used for small, lightweight data types, as limited size on stack.

·        Use for small, simple data types, like numbers, points or coordinates

·        Use when performance critical, to avoid heap allocation

Class

·        Reference type, stored on the heap

·        When you assign or pass as parameter it’s as a reference

·        Used for more complex data types that represent objects with multiple properties

·        Larger and more complex as allocated on the heap

·        Reference based equality comparison

·        Use classes when working with large objects or you need flexibility of the heap

Using statement v Using declaration

Using statements defines the scopes of unmanaged resources and disposes of them.

Using declaration has no brackets and disposes at the end of the method.

Encapsulation

Bundling of data and methods into a single unit, known as a class.  The internal state is hidden from the outside and interactions occur through well-defined interfaces.  Access modifiers (private, public, protected) control the visibility of class members, enforcing encapsulation. 

Inheritance

Define a new class on an existing class, which inherits attributes and behaviours from the base class.

Polymorphism

Treat classes as objects of a common base class.  This promotes flexibility as you work at a higher level of abstraction.  C# achieves this through interfaces, abstract classes and method overriding and overloading.

Abstraction

Simplifies complex systems by modelling at a higher level of abstraction, making them easier to read and maintain.  Provides clear separation between interface and implementation.

Abstract classes and interfaces define common contracts and provide a level of abstraction.

Interface: contract, no implementation

Abstract class: implementation, can contain mixture of:

·        abstract methods (must be overridden, no implementation)

·        concrete methods (no overriding, has implementation)

Normal class: can be extended or overridden

·        concrete methods (no overriding, has implementation)

·        virtual methods (may be overridden or extended, has implementation)

 

 

Association, Aggregation and Composition

Different types of relationships between classes and objects.

Association

Association represents a loose relationship with no strong ownership or dependency.  Example whereby a teacher may have many student and a student may have many teachers.

Aggregation

One class contains other classes, but these can exist on their own.  Example: A university may have many departments, but a department may exist independently. 

Composition

A stronger, tighter relationship.  This is a “contains a” relationship whereby the parts cannot exist without the whole.  This may be a car composed of an engine, seats etc.

Design patterns

Singleton: Ensures only one instance of a class and provides a global point of access to that instance.

Allows access control or when you want to coordinate actions across a system.

Examples: Logger, Cache.

Implementation:

 

Factory method: An interface for creating an object.  Allows subclasses to alter the type of objects being created.  Other creational methods are Abstract Factory, Builder (car builder), Prototype (clone).

Implementation:

Abstract Factory:

 

Builder method: separate the construction of a complex object from its representation, so the same construction method can create different representations.

Prototype method: involves creating new objects by copying existing objects. Copying objects for experimentation in a graphics program or copying a template.

Observer: Defines one-to-many dependency between objects so that when one object changes state all its dependents are notified and updated automatically. Example update a group chat; or weather monitoring system.

Strategy: Defines a family of algorithms, encapsulates each one, and makes them interchangeable.  Example choice of CSV, XML, JSON Exporter.

Decorator: Attaches additional responsibilities to an object dynamically. Provide a flexible alternative to subclassing for extending functionality.  Allows you to add new behaviours to an object without altering its structure.  Example is a coffee shop application whereby a basic coffee can have milk, sugar and different options.

Composite: composes objects into tree objects to represent part-whole hierarchies, allowing clients to treat individual objects and compositions of objects uniformly.  Example: graphic design, a complex image could be represented by composite shapes.  Altering one would alter the entire image.

Façade: Provides a simplified interface to a set of interfaces in a subsystem.  Abstracts the implementation.  Example is a calculate discount method whereby a user’s id just needs to be passed and the façade pattern will take care of the rest. In a multimedia application, a Facade pattern could be implemented to provide a simplified interface for playing different types of media files (audio, video) by abstracting the complexities of codec selection, file loading, and playback control.

Proxy: Solves access control issues. Provides a surrogate or placeholder for another object to control access to it, often used for implementing lazy loading, access control, or logging.

Bridge: Decouples an abstraction from its implementation so the two can vary independently. Example is a menu coupon being applied to a menu.

Visitor: Defines a new operation without changing the classes of the elements on which it operates.  Counting different types of documents.

Iterator: A way to aggregate object sequentially without exposing its underlying representation. Going through a collection of people or books.

State: Allow an object to alter its behaviour when its internal state changes.  Changing the state of a bank account which will prevent money being withdrawn.

Mediator: Centralises communication between objects. Forces objects to communicate via the mediator.  Useful in a chat application, managing who can chat with who.

Interpreter: defines a grammar for interpreting a language and provides an interpreter to evaluate sentences in that language. Converting a number to roman numerals.

Memento: captures and externalizes an object's internal state, allowing the object to be restored to this state later.  Example: text editor, being able to undo.

Command: encapsulates a request as an object, allowing parameterization of clients with different requests, queuing of requests, and the support of undoable operations.  Example: the Command pattern could be implemented where each button press corresponds to a command object (e.g., turn on TV, increase volume), enabling easy customization of remote controls and supporting undo functionality.

Template: defines the skeleton of an algorithm in the superclass but let’s subclasses override specific steps of the algorithm without changing its structure.  Example different ways of parsing an email.

Flyweight: minimizes memory usage or computational expenses by sharing as much as possible with related objects. In a text editor, the Flyweight pattern could be implemented to share common font and formatting information (such as style and size) among multiple characters, reducing the overall memory footprint for a large document.

Adaptor or Wrapper: convert the interface of a class into another interface client expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.  Example is converting an external city type to an internal city type.

Chain of responsibility: passes a request along a chain of handlers, where each handler decides either to process the request or to pass it to the next handler in the chain. In a workflow system, the Chain of Responsibility pattern could be implemented where each handler represents a step in the workflow, deciding whether to handle a task or pass it to the next step, allowing for flexible and dynamic task processing.

 

Dependency Injection (DI): Addresses issue of how components or classes obtain their dependencies.  Uses Inversion of Control (IoC), whereby control over object creation is inverted to a separate component.  Removes hard coded dependencies between components. Dependencies are abstracted away.  Makes it more reusable.  Improves quality of code.

Monolithic and microservices

Memory Management

Garbage collection is an automatic process in C#.   It uses a generational approach of Generation 0 (the most recent and short lived objects) being the first generation where most objects are deposed of.  If an object is not deposed from Gen 0 it is promoted to Gen 1 and then Gen 2.  These have less frequent and slower disposal processes and are for naturally longer living objects.  This all occurs on the heap memory. 

1)     Memory leaks

Memory leaks occur when objects are no longer needed, but are not properly released, preventing garbage collection.

·        Dispose unmanaged resources (file handles, database connections) by using the using statement.

·        Remove event handlers to prevent “linger references” that keep objects alive

·        Use memory profilers.

 

2)     Excessive memory consumption

Applications may consume more memory than is necessary.

·        Minimise object creation: reuse objects, use object pools, avoid temporary objects

·        Monitor memory usage

·        Use lightweight data structures when dealing with large datasets

 

3)     Large Object Heap Fragmentation
LOH is a special area for objects greater than 85,000 bytes.  Fragmentation in LOH can lead to performance issues.

4)     Reference holding
Objects may hold references to other objects longer than necessary preventing them from being collected.  Use WeakReference<T> for non-essential references to allow objects be collected.

5)     Inefficient data structures

Poorly chosen data structures can result in excessive memory usage.
Choose appropriate data structures, such as List<T> or LinkedList<T>.

Memory efficient collections: HashSet<T> for uniqueness or Dictionary for key value storage.

 

6)     Large number of objects in generations

If too many objects are in generation 1 and 2, then garbage collection can become slower.

·        Reduce the number of long lived objects to minimize unnecessary object promotion.

·        Implement proper object disposal.

7)     Excessive finalisation

Heavy use of finalisers can delay object reclaimation.  Use IDisposable.

8)     Pinning

Pinning objects (using GCHandle) can restrict the garbage collector’s ability to move objects.

 

Threads and Processes

Process

A process is an independent program that runs within its own memory space.  Each process is isolated from other processes.

Mostly there is one process per application.  Some applications, mainly server applications may consist of multiple processes.  These process can communicate to each other through IPC (inter process communication) mechanisms such as sockets or pipes.

Thread

A thread is the smallest unit of execution within a process. Multiple threads within the same process share the same memory space and resources.

Thread Class: allows you to create and manage threads within a process.

Thread Pool: manages a pool of worker threads.  Use to queue and execute tasks concurrently without manually creating and managing threads.

Async/Await: Write asynchronous code without managing threads.  Await completion of asynchronous tasks without blocking the main thread.  Good for I/O tasks rather than CPU tasks.

Thread Safety: Thread safety mechanisms include locks, mutexes, semaphones, concurrent queue and concurrent dictionary.  These help protect shared resources from concurrent access by multiple threads.

·        Locks: Ensure only on thread can execute a specific block of code at a time.  Use with small amount of code and sparingly or you might create deadlocks. . lock (balanceLock)

·        Mutex (Mutual exclusion): Similar to a lock.  Ensures that only one thread can access a protected resource at a time.  mut.WaitOne();

·        Semaphores: Control access to a resource based on the number of available permits, example limiting the number of concurrent connections to a database.
_pool = new Semaphore(initialCount: 0, maximumCount: 3);

·        Concurrent data structures: Specialized collections, example concurrent dictionary, with built in thread safety mechanisms like adding, removing and updating elements.  Use dictionary when you need to share data among multiple threads safely and efficiently.  Particularly useful where multiple threads need to access shared data without explicit locks.

 

Linked List is better when there are:

·        Frequent insertions and deletions, especially at the beginning of the list

·        No random access and more memory efficiency for smaller elements.

·        Iterating forward and backwards

However they have

·        Higher overhead

·        Slower random access

 

Linked List Node

Fundamental to linked list.  Each stores data and a reference to the next node (and the previous node in doubly linked list).

 

Linked List Node within a concurrent dictionary

Provides efficient removal of items during a concurrent operation.  Useful when you need to perform operations like removing specific items. 

Doesn’t need to search for the item.  Instead, you can quickly locate the LinkedListNode associated with the key and use it for removal.

A linkedlist data structure typically maintains the order in which elements were added.  By associating  a LinkedListNode with each key-value pair and adding these nodes to a linked list, you can preserve the insertion order of items in the concurrent dictionary.

When multiple threads are concurrently accessing or modifying a dictionary the LinkedListNode can serve as a way to track and manage the order of items safely and easily.

IDisposable

If Singleton class holds resources that require cleanup. 

 

 

 

 

Concurrent Dictionary

Thread safe: internal locks and find grained synchronisation to ensure safe concurrent access

Concurrent operations: Can perform Add, remove, update simultaneously without blocking each other.  Provides TryAdd, TryRemove, TryUpdate

Better performance in concurrent operations

 

Dictionary:

Not thread safe, single threaded access.

 

Mock tests

Isolation of code: test code in isolation of its dependencies

Faster

TDD

 

Attributes

[Obsolete("This method is deprecated. Use the NewMethod instead.")]

    static void OldMethod()

    {

        Console.WriteLine("This is the old method.");

    }

 

Dynamic variable is one where you don’t know the type of the variable at run time.

 

Events

public class ItemRemoval : EventArgs {

 

    public string Key { get; }

    public string Type { get; }

    public ItemRemoval(string key, string type)

    {

        Key = key;

        Type = type;

    }

 

}

 

public event EventHandler<ItemRemoval>? OnRemoval;

protected void RemoveItem() {

OnRemoval.Invoke(this, new ItemRemoval(cacheItem.CacheKey, cacheItem.CacheType));

}

Subscribed to event

var cache = ButlerCache.Instance;

cache.OnRemoval += ItemEvictionEvent;

 

IQueryable

You should use IQueryable in C# when dealing with data sources that support query composition, such as databases. IQueryable extends IEnumerable and allows you to build queries that are executed on the server side (e.g., in a database) rather than on the client side.

 

Using IQueryable can lead to more efficient queries because it enables deferred execution. This means that the query is not executed immediately when it's defined but is instead executed when the data is actually needed. This is particularly beneficial when working with large datasets.

 

Here's a quick example:

// Using IEnumerable (client-side execution)

IEnumerable<int> numbers = GetNumbers();

var result = numbers.Where(x => x > 5).ToList(); // Filter applied on the client side

 

// Using IQueryable (deferred execution)

IQueryable<int> numbersQuery = GetNumbers().AsQueryable();

var resultQuery = numbersQuery.Where(x => x > 5).ToList(); // Query is translated and executed on the server side

IQueryable is commonly used in LINQ to SQL or LINQ to Entities scenarios, where the query can be translated into a database-specific query and executed efficiently on the database server.

 

 

 

 

 

 

 

 

 

 

 

 

 

IValidateObject

Automatically validates an object

public class ValidateMe : IValidatableObject

{

    [Required]

    public bool Enable { get; set; }

 

    [Range(1, 5)]

    public int Prop1 { get; set; }

 

    [Range(1, 5)]

    public int Prop2 { get; set; }

 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)

    {

        var results = new List<ValidationResult>();

        if (this.Enable)

        {

            Validator.TryValidateProperty(this.Prop1,

                new ValidationContext(this, null, null) { MemberName = "Prop1" },

                results);

            Validator.TryValidateProperty(this.Prop2,

                new ValidationContext(this, null, null) { MemberName = "Prop2" },

                results);

 

            // some other random test

            if (this.Prop1 > this.Prop2)

            {

                results.Add(new ValidationResult("Prop1 must be larger than Prop2"));

            }

        }

        return results;

    }

}

 

public void DoValidation()
    {
        var toValidate = new ValidateMe()
        {
            Enable = true,
            Prop1 = 1,
            Prop2 = 2
        };
 
        bool validateAllProperties = false;
 
        var results = new List<ValidationResult>();
 
        bool isValid = Validator.TryValidateObject(
            toValidate,
            new ValidationContext(toValidate, null, null),
            results,
            validateAllProperties);
    }

 

 

 

String Builder

Strings are immutable. Each concatenation creates a whole new string.  StringBuilder is mutable.

Using `StringBuilder` in C# offers performance benefits when you need to concatenate multiple strings, especially within loops or when dealing with large amounts of text. The key advantages include:

 

1. **Mutable and Efficient Concatenation:** Strings in C# are immutable, meaning each concatenation operation creates a new string object. This can lead to performance issues, especially with frequent concatenation. `StringBuilder`, being mutable, allows you to efficiently modify the content without creating new instances.

 

2. **Reduced Memory Overhead:** Since `StringBuilder` allows you to modify the existing buffer, it avoids the overhead of creating multiple string objects. This can be crucial when dealing with large datasets, as it helps reduce memory usage.

 

3. **Better Performance in Loops:** When concatenating strings inside loops, using `StringBuilder` is significantly more efficient than using the `+` operator for concatenation. The latter creates a new string in each iteration, leading to poor performance and increased memory usage.

 

Here's a simple example to illustrate the difference:

 

```csharp

// Using StringBuilder

StringBuilder sb = new StringBuilder();

for (int i = 0; i < 10000; i++)

{

    sb.Append(i.ToString());

}

string result = sb.ToString();

 

// Using string concatenation

string resultConcat = "";

for (int i = 0; i < 10000; i++)

{

    resultConcat += i.ToString();

}

```

 

In the example above, the version with `StringBuilder` is more efficient and consumes less memory compared to the string concatenation version.

 

In summary, `StringBuilder` is particularly useful when you need to build or modify strings dynamically, especially in scenarios where performance and memory efficiency are important.

 

 

 

 

Event v Delegate

·        Delegate can be passed as a method parameter. 

·        Cannot override a delegate

·        Can override an Event

·        Event declared inside a class, while a delegate is declared outside a class.

·        Covariance and contravariance provide extra flexibility to delegate objects.  This means they declaration of parameters and return types can be more or less derived than originally specified. Declare a object but then pass in an string (covariance, string is more derived than an object) or declare a string and pass an object (contravariance, object is less derived than a string).

 

 

Delegates

Holds a reference to a method.  A way of telling which method to call when an event is triggered. 

Enables you to pass methods as parameters, create event handlers and define callback functions.

 

Lambdas

Inline methods

regular method:

int Multiply(int a, int b) {

    return a * b;

}

 

Lambda:

Func<int, int, int> multiply = (a, b) => a * b;

 

Anonymous methods

Define a method without naming it.  Essentially replaced by Lambdas and now used to create an instance of a delegate type.  You would only use an anonymous method over a lambda if you wanted to be explicit about the parameter and return types.

 

 

Yield: Used to minimise memory usage. Used with an iterator method.  Iterate over a whole collection without having to load the whole collection into memory.

Deferred execution: is the postponement of the execution of a sequence of operations until they are explicitly requested.  LINQ queries return IEnumerable and execution happens when they are converted to ToList.  Also Lazy<T> class defers instantiation of objects until requested.

 

Lazy loader example – only created when it is actually needed. 

public class ExpensiveObject

{

    public ExpensiveObject()

    {

        Console.WriteLine("ExpensiveObject is being created");

    }

 

    public void PerformSomeOperation()

    {

        Console.WriteLine("Performing some operation on ExpensiveObject");

    }

}

 

public class LazyLoaderExample

{

    private Lazy<ExpensiveObject> lazyExpensiveObject = new Lazy<ExpensiveObject>();

 

    public ExpensiveObject GetExpensiveObject()

    {

        return lazyExpensiveObject.Value;

    }

}

 

class Program

{

    static void Main()

    {

        LazyLoaderExample example = new LazyLoaderExample();

 

        // ExpensiveObject is not created until GetExpensiveObject is called

        ExpensiveObject expensiveObject = example.GetExpensiveObject();

 

        // Now the ExpensiveObject is created

        expensiveObject.PerformSomeOperation();

    }

}

 

Differences between an array and a list

Arrays have a lower memory overhead to lists.  So if you don’t need the convenience of dynamic resizing or convenience of adding and removing offered by lists then it’s better to use an array.  Mutability is more awkward with an array.

 

1)     Fixed Size vs. Dynamic Size:

Array: Arrays have a fixed size, meaning once you define the size of an array, you cannot change it.

List: Lists, on the other hand, can dynamically resize themselves. You can add or remove elements from a list without specifying the size beforehand.

2)     Mutability:

Array: Elements in an array cannot be easily added or removed once the array is created. You need to create a new array with a different size if you want to change the number of elements.

List: Lists are more flexible. You can easily add, remove, or insert elements without creating a new list.

3)     Performance:

 

Array: Arrays may have slightly better performance in certain scenarios since they are more lightweight and have a fixed size.

List: Lists provide more convenience and flexibility but may have a bit of overhead due to their dynamic resizing capability.

4)     Initialization:

Array: Arrays can be initialized using array initializer syntax: int[] myArray = {1, 2, 3};

List: Lists are typically initialized using the List<T> constructor or the Add method: List<int> myList = new List<int> {1, 2, 3};

5)     Methods and Properties:

Array: Arrays have a fixed set of methods and properties defined in the Array class.

List: Lists provide a more extensive set of methods and properties, such as Add, Remove, Count, etc., in the List<T> class.

6)     Memory Allocation:

Array: Arrays are allocated in a single contiguous block of memory.

List: Lists are implemented using an array internally, but they may need to allocate a new array and copy elements when resizing.

                                      

 

 

Potential questions:


Name some weaknesses and improvements:

- Regular expressions are a weakness.

 



Feedback:

 I'd suggest you try and figure out a way to more clearly and concisely communicate the trade off's for any technical decisions you're making or putting forward in an interview scenario. It was hard to gauge why you're choosing to build something one way or another, or use a serverless tech or API's etc at times and I struggled to get a feel for how you would make choices in a real world scenario. 


I find that since there's no real wrong or right answer in engineering for how we build things it's all about trade off's & pro's/con's based on the customer and the problem we're trying to solve. 

I'd suggest thinking about how to improve your thought structure and how you present your responses around this especially on system design/architecture design questions (e.g. when asked to provide some solution: 1. clarify customer requirements 2. communicate any assumptions you want to make / can make, suggest architecture or technology clearly communicating it's trade offs). You are clearly talented so my hunch is that it's a communication piece rather than anything to do with technical strength.

 

Questions:

What is your process and how would I fit into that?

How do you foster collaboration and a culture of camaraderie?

How do you ensure that highest standards of professional practice are obtained?

What challenges or exciting projects do you have in the next six months?

How do you support ongoing professional development?

 


Concurrency

the ability of a program to execute multiple tasks simultaneously

especially important for dealing with I/O bound operations and user interactions

Types of concurrency:

Multithreading: technique that allows multiple parts of a program to execute concurrently within a single process.

Threadpool enables multithreading.  Multiple threads within a single process.

often used for CPU bound tasks or parallel processing.


Async/Await: C# provides asynchronous programming support through the async and await keywords. This allows methods to execute asynchronously, freeing up the calling thread to perform other tasks while waiting for the asynchronous operation to complete.

Parallel LINQ (PLINQ): PLINQ enables parallel execution of LINQ queries, taking advantage of multiple cores and processors to improve performance.

var results = someCollection.AsParallel().Where(item => item.SomeCondition).ToList();


 Task Parallel Library (TPL): TPL provides a higher-level abstraction for parallelism in C#. It allows you to create and manage tasks more easily, providing features like task scheduling, cancellation, and continuation.

Task<int> task1 = Task.Run(() => SomeMethod());

Task<int> task2 = Task.Run(() => SomeOtherMethod());

Task.WaitAll(task1, task2);



Comments

Popular posts from this blog

Microservices and Service-Oriented Architecture

Version control and Continuous Integration/Continuous Deployment (CI/CD)

Delegates